/**
 * Test suite for the Section Editor implementation
 *
 * These tests verify the core business logic independently of the DOM,
 * ensuring reliability and consistency.
 */

const { Section, SectionManager, EditState, SectionType } = require('./section_editor.js');

describe('Section Class', () => {
    let section;

    beforeEach(() => {
        section = new Section('test-1', '# Test Header\n\nTest content', SectionType.HEADING);
    });

    describe('Constructor', () => {
        test('should initialize with correct properties', () => {
            expect(section.id).toBe('test-1');
            expect(section.originalMarkdown).toBe('# Test Header\n\nTest content');
            expect(section.currentMarkdown).toBe('# Test Header\n\nTest content');
            expect(section.sectionType).toBe(SectionType.HEADING);
            expect(section.state).toBe(EditState.ORIGINAL);
            expect(section.hasChanges()).toBe(false);
        });
    });

    describe('Edit State Management', () => {
        test('should start editing correctly', () => {
            const content = section.startEdit();
            expect(content).toBe('# Test Header\n\nTest content');
            expect(section.state).toBe(EditState.EDITING);
            expect(section.isEditing()).toBe(true);
        });

        test('should throw error when starting edit on already editing section', () => {
            section.startEdit();
            expect(() => section.startEdit()).toThrow('Section test-1 is already being edited');
        });

        test('should update content during editing', () => {
            section.startEdit();
            section.updateContent('# Modified Header\n\nModified content');

            // With new behavior: currentMarkdown stays unchanged until accept
            expect(section.currentMarkdown).toBe('# Test Header\n\nTest content');
            expect(section.editingMarkdown).toBe('# Modified Header\n\nModified content');
            expect(section.state).toBe(EditState.EDITING);
            expect(section.hasChanges()).toBe(false); // Because currentMarkdown != originalMarkdown check
        });

        test('should detect no changes when content is same as original', () => {
            section.startEdit();
            section.updateContent('# Test Header\n\nTest content');

            // With new behavior: state remains EDITING during edit session
            expect(section.state).toBe(EditState.EDITING);
            expect(section.hasChanges()).toBe(false); // currentMarkdown == originalMarkdown
        });

        test('should throw error when updating content on non-editing section', () => {
            expect(() => section.updateContent('new content')).toThrow('Section test-1 is not in editing state');
        });
    });

    describe('Accept Changes', () => {
        test('should accept changes correctly', () => {
            section.startEdit();
            section.updateContent('# Modified Header');
            const savedContent = section.acceptChanges();

            expect(savedContent).toBe('# Modified Header');
            // State becomes SAVED after accepting changes
            expect(section.state).toBe(EditState.SAVED);
            expect(section.lastSaved).toBeInstanceOf(Date);
        });

        test('should throw error when accepting changes on non-editing section', () => {
            expect(() => section.acceptChanges()).toThrow('Section test-1 is not in editing state');
        });
    });

    describe('Cancel Changes', () => {
        test('should cancel changes and revert to original', () => {
            section.startEdit();
            section.updateContent('# Modified Header');
            const originalContent = section.cancelChanges();

            expect(originalContent).toBe('# Test Header\n\nTest content');
            expect(section.currentMarkdown).toBe('# Test Header\n\nTest content');
            expect(section.state).toBe(EditState.ORIGINAL);
            expect(section.hasChanges()).toBe(false);
        });
    });

    describe('Reset to Original', () => {
        test('should reset to original and exit editing mode', () => {
            section.startEdit();
            section.updateContent('# Modified Header');
            const originalContent = section.resetToOriginal();

            expect(originalContent).toBe('# Test Header\n\nTest content');
            expect(section.currentMarkdown).toBe('# Test Header\n\nTest content');
            expect(section.state).toBe(EditState.ORIGINAL);
            expect(section.isEditing()).toBe(false);
        });

        test('should reset from any state', () => {
            // Reset can be called from any state, not just editing
            const result = section.resetToOriginal();
            expect(result).toBe('# Test Header\n\nTest content');
            expect(section.state).toBe(EditState.ORIGINAL);
        });
    });

    describe('Stop Editing', () => {
        test('should stop editing and preserve changes as modified', () => {
            section.startEdit();
            section.updateContent('# Modified Header');
            const newState = section.stopEditing();

            expect(newState).toBe(EditState.MODIFIED);
            expect(section.state).toBe(EditState.MODIFIED);
            expect(section.isEditing()).toBe(false);
            expect(section.isModified()).toBe(true);
        });

        test('should stop editing and revert to original if no changes', () => {
            section.startEdit();
            const newState = section.stopEditing();

            expect(newState).toBe(EditState.ORIGINAL);
            expect(section.state).toBe(EditState.ORIGINAL);
        });
    });

    describe('Static Methods', () => {
        test('should generate stable IDs', () => {
            const id1 = Section.generateId('Test content', 0);
            const id2 = Section.generateId('Test content', 0);
            const id3 = Section.generateId('Test content', 1);

            expect(id1).toBe(id2); // Same content, same position
            expect(id1).not.toBe(id3); // Same content, different position
            expect(id1).toMatch(/^section_\d+_0$/);
        });

        test('should detect section types correctly', () => {
            expect(Section.detectType('# Heading')).toBe(SectionType.HEADING);
            expect(Section.detectType('```code```')).toBe(SectionType.CODE);
            expect(Section.detectType('> Quote')).toBe(SectionType.BLOCKQUOTE);
            expect(Section.detectType('- List item')).toBe(SectionType.LIST);
            expect(Section.detectType('1. Numbered list')).toBe(SectionType.LIST);
            expect(Section.detectType('Regular paragraph')).toBe(SectionType.PARAGRAPH);
        });
    });

    describe('Status Methods', () => {
        test('should return correct status', () => {
            const status = section.getStatus();

            expect(status).toEqual({
                id: 'test-1',
                state: EditState.ORIGINAL,
                hasChanges: false,
                isEditing: false,
                isModified: false,
                contentLength: section.originalMarkdown.length, // Dynamic length
                lastSaved: null,
                sectionType: SectionType.HEADING
            });
        });
    });
});

describe('SectionManager Class', () => {
    let manager;

    beforeEach(() => {
        manager = new SectionManager();
    });

    describe('Section Creation', () => {
        test('should create sections from markdown', () => {
            const markdown = `# First Header

First paragraph content.

## Second Header

Second paragraph content.

### Third Header

Third paragraph content.`;

            const sections = manager.createSectionsFromMarkdown(markdown);

            expect(sections).toHaveLength(6); // Each heading and paragraph is a section
            expect(sections[0].sectionType).toBe(SectionType.HEADING);
            expect(sections[1].sectionType).toBe(SectionType.PARAGRAPH);
            expect(sections[2].sectionType).toBe(SectionType.HEADING);
            expect(sections[3].sectionType).toBe(SectionType.PARAGRAPH);
            expect(sections[4].sectionType).toBe(SectionType.HEADING);
            expect(sections[5].sectionType).toBe(SectionType.PARAGRAPH);
        });

        test('should emit sections-created event', () => {
            const eventCallback = jest.fn();
            manager.on('sections-created', eventCallback);

            const markdown = `# Header\n\nParagraph.`;
            manager.createSectionsFromMarkdown(markdown);

            expect(eventCallback).toHaveBeenCalledWith({
                sections: expect.any(Array),
                count: 2
            });
        });
    });

    describe('Edit Management', () => {
        beforeEach(() => {
            const markdown = `# Header One\n\nFirst paragraph.\n\n## Header Two\n\nSecond paragraph.`;
            manager.createSectionsFromMarkdown(markdown);
        });

        test('should start editing a section', () => {
            const sections = manager.getAllSections();
            const sectionId = sections[0].id;

            const content = manager.startEditing(sectionId);

            expect(content).toBe('# Header One');
            expect(manager.editingSection).toBe(sectionId);
            expect(sections[0].isEditing()).toBe(true);
        });

        test('should emit edit-started event', () => {
            const eventCallback = jest.fn();
            manager.on('edit-started', eventCallback);

            const sections = manager.getAllSections();
            const sectionId = sections[0].id;

            manager.startEditing(sectionId);

            expect(eventCallback).toHaveBeenCalledWith({
                sectionId,
                content: '# Header One',
                section: expect.any(Object)
            });
        });

        test('should stop editing previous section when starting new one', () => {
            const sections = manager.getAllSections();
            const section1Id = sections[0].id;
            const section2Id = sections[1].id;

            manager.startEditing(section1Id);
            manager.updateContent(section1Id, '# Modified Header');

            // Start editing second section
            manager.startEditing(section2Id);

            expect(manager.editingSection).toBe(section2Id);
            expect(sections[0].isModified()).toBe(true);
            expect(sections[0].isEditing()).toBe(false);
            expect(sections[1].isEditing()).toBe(true);
        });

        test('should update content correctly', () => {
            const sections = manager.getAllSections();
            const sectionId = sections[0].id;

            manager.startEditing(sectionId);
            manager.updateContent(sectionId, '# Modified Header');

            // With new behavior: currentMarkdown stays unchanged until accept
            expect(sections[0].editingMarkdown).toBe('# Modified Header');
            expect(sections[0].isEditing()).toBe(true);
        });

        test('should accept changes correctly', () => {
            const sections = manager.getAllSections();
            const sectionId = sections[0].id;

            manager.startEditing(sectionId);
            manager.updateContent(sectionId, '# Modified Header');
            const savedContent = manager.acceptChanges(sectionId);

            expect(savedContent).toBe('# Modified Header');
            expect(sections[0].state).toBe(EditState.SAVED);
            expect(manager.editingSection).toBeNull();
        });

        test('should cancel changes correctly', () => {
            const sections = manager.getAllSections();
            const sectionId = sections[0].id;

            manager.startEditing(sectionId);
            manager.updateContent(sectionId, '# Modified Header');
            const originalContent = manager.cancelChanges(sectionId);

            expect(originalContent).toBe('# Header One');
            expect(sections[0].state).toBe(EditState.ORIGINAL);
            expect(manager.editingSection).toBeNull();
        });

        test('should reset to original correctly', () => {
            const sections = manager.getAllSections();
            const sectionId = sections[0].id;

            manager.startEditing(sectionId);
            manager.updateContent(sectionId, '# Modified Header');
            const originalContent = manager.resetToOriginal(sectionId);

            expect(originalContent).toBe('# Header One');
            expect(sections[0].currentMarkdown).toBe('# Header One');
            expect(sections[0].isEditing()).toBe(false); // Reset exits editing mode
            expect(sections[0].state).toBe(EditState.ORIGINAL);
        });
    });

    describe('Document Status', () => {
        beforeEach(() => {
            const markdown = `# Header\n\nParagraph one.\n\n## Second\n\nParagraph two.`;
            manager.createSectionsFromMarkdown(markdown);
        });

        test('should return correct document status', () => {
            const sections = manager.getAllSections();

            // Start editing and modify one section
            manager.startEditing(sections[0].id);
            manager.updateContent(sections[0].id, '# Modified');

            // Accept changes on another section
            manager.startEditing(sections[1].id);
            manager.acceptChanges(sections[1].id);

            const status = manager.getDocumentStatus();

            expect(status.totalSections).toBe(4);
            expect(status.modifiedSections).toBe(1);
            expect(status.editingSections).toBe(0);
            expect(status.savedSections).toBe(1);
            expect(status.hasUnsavedChanges).toBe(true);
        });

        test('should get sections by state', () => {
            const sections = manager.getAllSections();

            manager.startEditing(sections[0].id);
            manager.updateContent(sections[0].id, '# Modified');
            manager.stopEditing(sections[0].id);

            const modifiedSections = manager.getSectionsByState(EditState.MODIFIED);
            const originalSections = manager.getSectionsByState(EditState.ORIGINAL);

            expect(modifiedSections).toHaveLength(1);
            expect(originalSections).toHaveLength(3);
        });

        test('should reset all sections to original', () => {
            const sections = manager.getAllSections();

            // Modify multiple sections
            manager.startEditing(sections[0].id);
            manager.updateContent(sections[0].id, '# Modified');
            manager.stopEditing(sections[0].id);

            manager.startEditing(sections[1].id);
            manager.updateContent(sections[1].id, 'Modified paragraph');

            manager.resetAllToOriginal();

            const status = manager.getDocumentStatus();
            expect(status.hasUnsavedChanges).toBe(false);
            expect(status.modifiedSections).toBe(0);
            expect(status.editingSections).toBe(0);
        });

        test('should generate complete document markdown', () => {
            const markdown = manager.getDocumentMarkdown();

            expect(markdown).toContain('# Header');
            expect(markdown).toContain('Paragraph one.');
            expect(markdown).toContain('## Second');
            expect(markdown).toContain('Paragraph two.');
        });
    });

    describe('Error Handling', () => {
        test('should throw error for non-existent section operations', () => {
            expect(() => manager.startEditing('non-existent')).toThrow('Section non-existent not found');
            expect(() => manager.updateContent('non-existent', 'content')).toThrow('Section non-existent not found');
            expect(() => manager.acceptChanges('non-existent')).toThrow('Section non-existent not found');
            expect(() => manager.cancelChanges('non-existent')).toThrow('Section non-existent not found');
        });
    });

    describe('Event System', () => {
        test('should handle multiple listeners for same event', () => {
            const callback1 = jest.fn();
            const callback2 = jest.fn();

            manager.on('test-event', callback1);
            manager.on('test-event', callback2);

            manager.emit('test-event', { data: 'test' });

            expect(callback1).toHaveBeenCalledWith({ data: 'test' });
            expect(callback2).toHaveBeenCalledWith({ data: 'test' });
        });
    });
});

describe('Integration Tests', () => {
    test('should handle complex editing workflow', () => {
        const manager = new SectionManager();
        const markdown = `# Document Title

Introduction paragraph with some content.

## First Section

Content of the first section.

## Second Section

Content of the second section.

### Subsection

Nested content here.`;

        // Create sections
        const sections = manager.createSectionsFromMarkdown(markdown);
        expect(sections).toHaveLength(8); // Title + intro + 2*(section header + content) + subsection header + content

        // Edit multiple sections
        const section1Id = sections[1].id; // Introduction paragraph
        const section3Id = sections[3].id; // First section content

        // Start editing first section
        manager.startEditing(section1Id);
        manager.updateContent(section1Id, 'Modified introduction with new content.');

        // Switch to editing second section (should preserve first as modified)
        manager.startEditing(section3Id);
        manager.updateContent(section3Id, 'Modified first section content.');

        // Check states - section 1 should have pending changes after being stopped
        expect(sections[1].isEditing()).toBe(false); // Not currently editing (was stopped when we started editing section 3)
        expect(sections[1].isModified()).toBe(true); // Should have pending changes preserved
        expect(sections[3].isEditing()).toBe(true); // Currently editing

        // Check that they have the right content
        expect(sections[3].editingMarkdown).toBe('Modified first section content.');
        expect(sections[1].pendingMarkdown).toBe('Modified introduction with new content.'); // Pending changes preserved

        // Accept changes on second section
        manager.acceptChanges(section3Id);
        expect(sections[3].state).toBe(EditState.SAVED);

        // Reset first section to discard its pending changes
        manager.resetToOriginal(section1Id);
        expect(sections[1].state).toBe(EditState.ORIGINAL);

        // Verify document status
        const status = manager.getDocumentStatus();
        expect(status.savedSections).toBe(1);
        expect(status.modifiedSections).toBe(0);
        expect(status.hasUnsavedChanges).toBe(false);
    });
});

describe('Action Semantics - Core Behavior', () => {
    describe('Section Action Behavior', () => {
        let section;
        const ORIGINAL = '# Original Header\n\nOriginal content';
        const MODIFIED_1 = '# Modified Header 1\n\nModified content 1';
        const MODIFIED_2 = '# Modified Header 2\n\nModified content 2';

        beforeEach(() => {
            section = new Section('test', ORIGINAL, SectionType.HEADING);
        });

        test('original content is never modified', () => {
            // Start editing
            section.startEdit();
            section.updateContent(MODIFIED_1);
            expect(section.originalMarkdown).toBe(ORIGINAL);

            // Accept changes
            section.acceptChanges();
            expect(section.originalMarkdown).toBe(ORIGINAL);

            // Reset should return to original
            section.resetToOriginal();
            expect(section.originalMarkdown).toBe(ORIGINAL);
            expect(section.currentMarkdown).toBe(ORIGINAL);
        });

        test('reset returns to original state from render time', () => {
            // Make some changes and accept them
            section.startEdit();
            section.updateContent(MODIFIED_1);
            section.acceptChanges();
            expect(section.currentMarkdown).toBe(MODIFIED_1);

            // Reset should go back to original, not the accepted changes
            section.resetToOriginal();
            expect(section.currentMarkdown).toBe(ORIGINAL);
            expect(section.state).toBe(EditState.ORIGINAL);
            expect(section.hasChanges()).toBe(false);
        });

        test('cancel returns to state before editing started', () => {
            // Accept some changes first
            section.startEdit();
            section.updateContent(MODIFIED_1);
            section.acceptChanges();
            expect(section.currentMarkdown).toBe(MODIFIED_1);
            expect(section.state).toBe(EditState.SAVED);

            // Start editing again and make different changes
            section.startEdit();
            section.updateContent(MODIFIED_2);

            // Cancel should return to the state before this edit session (SAVED)
            const result = section.cancelChanges();
            expect(result).toBe(MODIFIED_1);
            expect(section.currentMarkdown).toBe(MODIFIED_1);
            expect(section.state).toBe(EditState.SAVED); // Back to saved state
        });

        test('accept makes current changes the new current content', () => {
            // Start editing and make changes
            section.startEdit();
            section.updateContent(MODIFIED_1);

            // Accept should make MODIFIED_1 the new current content
            const result = section.acceptChanges();
            expect(result).toBe(MODIFIED_1);
            expect(section.currentMarkdown).toBe(MODIFIED_1);
            expect(section.state).toBe(EditState.SAVED); // Explicitly saved

            // Now start editing again
            section.startEdit();
            section.updateContent(MODIFIED_2);

            // Cancel should return to MODIFIED_1 (the last accepted state)
            section.cancelChanges();
            expect(section.currentMarkdown).toBe(MODIFIED_1);
            expect(section.state).toBe(EditState.SAVED); // Back to saved state
        });

        test('multiple edit sessions preserve state correctly', () => {
            // Session 1: Edit and accept
            section.startEdit();
            section.updateContent(MODIFIED_1);
            section.acceptChanges();
            expect(section.currentMarkdown).toBe(MODIFIED_1);

            // Session 2: Edit and cancel
            section.startEdit();
            section.updateContent(MODIFIED_2);
            section.cancelChanges();
            expect(section.currentMarkdown).toBe(MODIFIED_1); // Back to accepted state

            // Session 3: Edit and accept again
            section.startEdit();
            section.updateContent(MODIFIED_2);
            section.acceptChanges();
            expect(section.currentMarkdown).toBe(MODIFIED_2);

            // Reset should still go to original
            section.resetToOriginal();
            expect(section.currentMarkdown).toBe(ORIGINAL);
        });

        test('editing state tracks editingMarkdown separately', () => {
            section.startEdit();
            expect(section.editingMarkdown).toBe(ORIGINAL); // Baseline for editing

            section.updateContent(MODIFIED_1);
            expect(section.editingMarkdown).toBe(MODIFIED_1);
            expect(section.currentMarkdown).toBe(ORIGINAL); // Unchanged until accept

            section.acceptChanges();
            expect(section.editingMarkdown).toBe(null); // Cleared after accept
            expect(section.currentMarkdown).toBe(MODIFIED_1); // Now updated
        });
    });
});