/**
 * L5X RLL Exporter — Allen Bradley Studio 5000 / Logix5000 format.
 *
 * Converts a Lego Ladder Program back into L5X XML with RLL content.
 * Produces a valid L5X file that can be imported into Studio 5000.
 */

import type {
  Program, Rung, RungElement, Contact, Coil, Timer, Counter,
  LogicGate, CompareElement, MoveElement, MathElement,
  CoilType
} from './types';

// ─── Type mapping ─────────────────────────────────────────────────

function coilTypeToL5X(coilType: CoilType): string {
  switch (coilType) {
    case 'OTE':
    case 'OUTPUT':
      return 'OTE';
    case 'SET':
    case 'LATCH':
      return 'OTL';
    case 'RESET':
    case 'UNLATCH':
      return 'OTU';
    case 'TOGGLE':
      // No direct L5X equivalent — approximate as OTE
      return 'OTE';
    default:
      return 'OTE';
  }
}

// ─── Element → L5X mnemonic ──────────────────────────────────────

function elementToMnemonic(el: RungElement): string {
  switch (el.type) {
    case 'contact': {
      const c = el as Contact;
      return c.contactType === 'NO' ? `XIC(${c.address})` : `XIO(${c.address})`;
    }

    case 'coil': {
      const c = el as Coil;
      const op = coilTypeToL5X(c.coilType);
      return `${op}(${c.address})`;
    }

    case 'timer': {
      const t = el as Timer;
      const presetSec = (t.preset / 1000).toFixed(1);
      return `${t.timerType}(${t.instanceId},.EN,.TT,${presetSec})`;
    }

    case 'counter': {
      const ct = el as Counter;
      return `${ct.counterType}(${ct.instanceId},.CD,.DN,${ct.preset})`;
    }

    case 'gate': {
      const g = el as LogicGate;
      const inputs = g.inputs.length >= 2 ? g.inputs.join(',') : g.inputs[0];
      return `${g.gateType}(${inputs},${g.outputAddress})`;
    }

    case 'compare': {
      const cmp = el as CompareElement;
      const opMap: Record<string, string> = {
        '==': 'EQU', '!=': 'NEQ', '>': 'GRT',
        '<': 'LES', '>=': 'GEQ', '<=': 'LEQ',
      };
      const l5xOp = opMap[cmp.op] || 'EQU';
      return `${l5xOp}(${cmp.inputA},${cmp.inputB})`;
    }

    case 'math': {
      const m = el as MathElement;
      return `${m.operator}(${m.inputA},${m.inputB},${m.outputAddress})`;
    }

    case 'move': {
      const mv = el as MoveElement;
      return `MOV(${mv.source},${mv.destination})`;
    }

    case 'oneshot': {
      return `OSR(${(el as any).address})`;
    }

    case 'scale': {
      const s = el as any;
      return `SLC(${s.inputAddress},${s.inMin},${s.inMax},${s.outMin},${s.outMax},${s.destination})`;
    }

    case 'noop':
    case 'branch':
    default:
      return '';
  }
}

// ─── Rung → L5X text ─────────────────────────────────────────────

function rungToText(rung: Rung): string {
  const parts: string[] = [];

  // All elements are in series[] — coil is typically the last element
  for (const el of rung.series) {
    const mnemonic = elementToMnemonic(el);
    if (mnemonic) parts.push(mnemonic);
  }

  // Semicolon terminates the rung
  return parts.join(' ') + ';';
}

// ─── Program → L5X XML ───────────────────────────────────────────

function escapeXml(text: string): string {
  return text
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;');
}

function programToL5X(program: Program): string {
  const lines: string[] = [];

  lines.push(`<Program Name="${escapeXml(program.name)}" Reentrant="false" ExecutionPeriod="1" ExecutionPeriodTimeBasis="MS" DisableUpdateOutputs="false">`);
  lines.push(`  <Routine Name="${escapeXml(program.name)}" Type="RLL">`);
  lines.push(`    <RLLComment><Text><![CDATA[Exported from Lego Ladder]]></Text></RLLComment>`);
  lines.push(`    <RLLContent>`);

  for (let i = 0; i < program.rungs.length; i++) {
    const rung = program.rungs[i];
    const text = rungToText(rung);
    const rungNum = rung.id.replace('rung-', '') || String(i);

    lines.push(`      <Rung Number="${rungNum}" Type="N">`);
    lines.push(`        <Text><![CDATA[${escapeXml(text)}]]></Text>`);
    lines.push(`      </Rung>`);
  }

  lines.push(`    </RLLContent>`);
  lines.push(`  </Routine>`);
  lines.push(`</Program>`);

  return lines.join('\n');
}

// ─── Full L5X document ───────────────────────────────────────────

/**
 * Generate a complete L5X XML document from one or more Programs.
 * The output can be imported into Studio 5000.
 */
export function exportToL5X(programs: Program[]): string {
  const now = new Date();
  const timestamp = now.toISOString().replace('T', ' ').substring(0, 19);

  const lines: string[] = [];
  lines.push(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`);
  lines.push(`<RSLogix5000Content SchemaRevision="1.0" SoftwareRevision="32.01" TargetName="LegoLadder_Export" TargetType="Controller" ContainsContext="false" Owner="Lego Ladder" ExportDate="${timestamp}" ExportOptions="NoRawData L5KData DecoratedData">`);
  lines.push(`  <Controller Use="Target" Name="LegoLadder_Export" ProcessorType="5570-L830E" MajorRev="32" MinorRev="1" ProjectCreationDate="${timestamp}">`);
  lines.push(`    <RedundancyInfo Enabled="false"/>`);
  lines.push(`    <Security/>`);
  lines.push(`    <DataTypes/>`);
  lines.push(`    <Programs>`);

  for (const program of programs) {
    lines.push(programToL5X(program));
  }

  lines.push(`    </Programs>`);
  lines.push(`    <Tasks>`);
  lines.push(`      <Task Name="MainTask" Type="CONTINUOUS" Priority="1" Watchdog="500">`);
  lines.push(`        <ScheduledPrograms>`);
  for (const program of programs) {
    lines.push(`          <ScheduledProgram Name="${escapeXml(program.name)}"/>`);
  }
  lines.push(`        </ScheduledPrograms>`);
  lines.push(`      </Task>`);
  lines.push(`    </Tasks>`);
  lines.push(`  </Controller>`);
  lines.push(`</RSLogix5000Content>`);

  return lines.join('\n');
}

/**
 * Export a single program to L5X format.
 */
export function exportProgramToL5X(program: Program): string {
  return exportToL5X([program]);
}

/**
 * Generate a downloadable .l5x file blob from a program.
 */
export function downloadL5X(program: Program, filename: string = 'program.l5x'): void {
  const content = exportProgramToL5X(program);
  const blob = new Blob([content], { type: 'application/xml' });
  const url = URL.createObjectURL(blob);

  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
}