import { describe, it, expect } from 'vitest';
import { importL5X, parseL5X } from './l5x-importer';
import { exportToL5X, exportProgramToL5X } from './l5x-exporter';

// ─── Minimal L5X fixtures ─────────────────────────────────────────

const minimalL5X = `<?xml version="1.0" encoding="UTF-8"?>
<RSLogix5000Content>
  <Controller>
    <Programs>
      <Program Name="MainProg" Reentrant="false">
        <Routine Name="MainProg" Type="RLL">
          <RLLContent>
            <Rung Number="0" Type="N"><Text><![CDATA[XIC(I:0/0) OTE(O:0/0);]]></Text></Rung>
            <Rung Number="1" Type="N"><Text><![CDATA[XIO(I:0/1) OTL(O:0/1);]]></Text></Rung>
            <Rung Number="2" Type="N"><Text><![CDATA[XIC(I:0/0) TON(T4:0,.EN,.TT,5.0);]]></Text></Rung>
          </RLLContent>
        </Routine>
      </Program>
    </Programs>
  </Controller>
</RSLogix5000Content>`;

const complexL5X = `<?xml version="1.0" encoding="UTF-8"?>
<RSLogix5000Content>
  <Controller>
    <Programs>
      <Program Name="ComplexProg" Reentrant="false">
        <Routine Name="ComplexProg" Type="RLL">
          <RLLContent>
            <Rung Number="0" Type="N"><Text><![CDATA[XIC(I:0/0) XIC(I:0/1) GRT(N7:0,100) OTE(O:0/0);]]></Text></Rung>
            <Rung Number="1" Type="N"><Text><![CDATA[[XIC(I:0/0),XIC(I:0/1)] OTE(O:0/2);]]></Text></Rung>
            <Rung Number="2" Type="N"><Text><![CDATA[XIC(I:0/0) MOV(N7:0,N7:1);]]></Text></Rung>
            <Rung Number="3" Type="N"><Text><![CDATA[XIC(I:0/0) ADD(N7:0,N7:1,N7:2);]]></Text></Rung>
            <Rung Number="4" Type="N"><Text><![CDATA[XIC(I:0/0) EQU(N7:0,50);]]></Text></Rung>
          </RLLContent>
        </Routine>
      </Program>
    </Programs>
  </Controller>
</RSLogix5000Content>`;

// ─── Tests ────────────────────────────────────────────────────────

describe('L5X Importer', () => {
  describe('importL5X', () => {
    it('parses a simple XIC/OTE rung', () => {
      const program = importL5X(minimalL5X);
      expect(program).not.toBeNull();
      expect(program!.name).toBe('MainProg');
      expect(program!.rungs.length).toBe(3);
    });

    it('correctly identifies XIC as NO contact', () => {
      const program = importL5X(minimalL5X);
      const rung0 = program!.rungs[0];
      expect(rung0.series.length).toBe(2);
      expect(rung0.series[0].type).toBe('contact');
      expect((rung0.series[0] as any).contactType).toBe('NO');
    });

    it('correctly identifies XIO as NC contact', () => {
      const program = importL5X(minimalL5X);
      const rung1 = program!.rungs[1];
      expect(rung1.series[0].type).toBe('contact');
      expect((rung1.series[0] as any).contactType).toBe('NC');
    });

    it('correctly identifies OTE as OTE coil', () => {
      const program = importL5X(minimalL5X);
      const rung0 = program!.rungs[0];
      const coil = rung0.series[rung0.series.length - 1];
      expect(coil.type).toBe('coil');
      expect((coil as any).coilType).toBe('OTE');
      expect((coil as any).address).toBe('O:0/0');
    });

    it('correctly identifies OTL as SET coil', () => {
      const program = importL5X(minimalL5X);
      const rung1 = program!.rungs[1];
      const coil = rung1.series[rung1.series.length - 1];
      expect((coil as any).coilType).toBe('SET');
    });

    it('parses TON timer', () => {
      const program = importL5X(minimalL5X);
      const rung2 = program!.rungs[2];
      const timer = rung2.series.find(el => el.type === 'timer');
      expect(timer).toBeDefined();
      expect((timer as any).timerType).toBe('TON');
      expect((timer as any).instanceId).toBe('T4:0');
      expect((timer as any).preset).toBe(5000);
    });

    it('handles complex rungs with multiple contacts', () => {
      const program = importL5X(complexL5X);
      expect(program).not.toBeNull();
      expect(program!.rungs.length).toBe(5);

      // Rung 0: XIC XIC GRT OTE
      const rung0 = program!.rungs[0];
      expect(rung0.series.length).toBeGreaterThanOrEqual(3);
    });

    it('handles parallel branches [A,B]', () => {
      const program = importL5X(complexL5X);
      const rung1 = program!.rungs[1];
      expect(rung1.series.length).toBeGreaterThanOrEqual(2);
    });

    it('handles MOV instruction', () => {
      const program = importL5X(complexL5X);
      const rung2 = program!.rungs[2];
      const moveEl = rung2.series.find(e => e.type === 'move');
      expect(moveEl).not.toBeNull();
    });

    it('handles ADD instruction', () => {
      const program = importL5X(complexL5X);
      const rung3 = program!.rungs[3];
      const mathEl = rung3.series.find(e => e.type === 'math');
      expect(mathEl).not.toBeNull();
      expect((mathEl as any).operator).toBe('ADD');
    });

    it('handles EQU compare', () => {
      const program = importL5X(complexL5X);
      const rung4 = program!.rungs[4];
      const cmpEl = rung4.series.find(e => e.type === 'compare');
      expect(cmpEl).not.toBeNull();
      expect((cmpEl as any).op).toBe('==');
    });
  });

  describe('parseL5X', () => {
    it('returns array of all programs', () => {
      const programs = parseL5X(minimalL5X);
      expect(programs.length).toBe(1);
      expect(programs[0].name).toBe('MainProg');
    });
  });

  describe('L5X Exporter', () => {
    it('exports a program back to valid XML', () => {
      const program = importL5X(minimalL5X)!;
      const xml = exportProgramToL5X(program);

      expect(xml).toContain('<?xml');
      expect(xml).toContain('<RSLogix5000Content');
      expect(xml).toContain('MainProg');
      expect(xml).toContain('XIC');
      expect(xml).toContain('OTE');
    });

    it('exports OTL for SET coil type', () => {
      const program = importL5X(minimalL5X)!;
      const xml = exportProgramToL5X(program);
      expect(xml).toContain('OTL(O:0/1)');
    });

    it('exports multiple programs', () => {
      const programs = parseL5X(minimalL5X);
      const xml = exportToL5X(programs);
      expect(xml).toContain('<ScheduledProgram Name="MainProg"/>');
    });

    it('produces round-trip-compatible output', () => {
      // Import → Export → Import again
      const original = importL5X(minimalL5X)!;
      const xml = exportProgramToL5X(original);
      const reImported = importL5X(xml);

      expect(reImported).not.toBeNull();
      expect(reImported!.rungs.length).toBe(original.rungs.length);

      // Check that rung series match
      for (let i = 0; i < original.rungs.length; i++) {
        expect(reImported!.rungs[i].series.length).toBe(original.rungs[i].series.length);
      }
    });
  });
});
