#!/usr/bin/env tsx

import { PCB } from '@typecad/typecad';
import { TrackBuilder } from '@typecad/typecad';
import fsexp from 'fast-sexpr';
import fs from 'node:fs';
import chalk from 'chalk';

interface KicadSegment {
    start: { x: number; y: number };
    end: { x: number; y: number };
    width: number;
    layer: string;
    net?: number;
    uuid?: string;
}

function parseSexprItem(item: any[]): KicadSegment | null {
    if (!Array.isArray(item) || item[0] !== 'segment') {
        return null;
    }
    let start: { x: number; y: number } | undefined;
    let end: { x: number; y: number } | undefined;
    let width: number = 0.2;
    let layer: string = "F.Cu";
    let net: number | undefined;
    let uuid: string | undefined;
    for (let i = 1; i < item.length; i++) {
        const element = item[i];
        if (Array.isArray(element) && element.length > 0) {
            const key = element[0];
            const value = element.slice(1);
            switch (key) {
                case 'start':
                    if (value.length === 2) {
                        const x = parseFloat(value[0]);
                        const y = parseFloat(value[1]);
                        if (!isNaN(x) && !isNaN(y)) {
                            start = { x, y };
                        }
                    }
                    break;
                case 'end':
                    if (value.length === 2) {
                        const x = parseFloat(value[0]);
                        const y = parseFloat(value[1]);
                        if (!isNaN(x) && !isNaN(y)) {
                            end = { x, y };
                        }
                    }
                    break;
                case 'width':
                    if (value.length === 1) {
                        const w = parseFloat(value[0]);
                        if (!isNaN(w)) {
                            width = w;
                        }
                    }
                    break;
                case 'layer':
                    if (value.length === 1 && typeof value[0] === 'string') {
                        layer = value[0].replace(/"/g, '');
                    }
                    break;
                case 'net':
                    if (value.length === 1) {
                        const n = parseInt(value[0], 10);
                        if (!isNaN(n)) {
                            net = n;
                        }
                    }
                    break;
                case 'uuid':
                     if (value.length === 1 && typeof value[0] === 'string') {
                        uuid = value[0].replace(/"/g, '');
                    }
                    break;
            }
        }
    }
    if (start && end) {
        const result: KicadSegment = { start, end, width, layer, net, uuid };
        return result;
    }
    return null;
}

function findSegments(parsedData: any, depth = 0): KicadSegment[] {
    const segments: KicadSegment[] = [];
    if (Array.isArray(parsedData)) {
        for (let i = 0; i < parsedData.length; i++) {
            const item = parsedData[i];
            if (Array.isArray(item) && item.length > 0 && item[0] === 'segment') {
                const segment = parseSexprItem(item);
                if (segment) {
                    segments.push(segment);
                }
            } else if (Array.isArray(item)) {
                segments.push(...findSegments(item, depth + 1));
            }
        }
    }
    return segments;
}

interface KicadComponentPlacement {
    reference: string;
    x: number;
    y: number;
    rotation: number;
    layer: string;
}

function parseFootprintItem(item: any[]): KicadComponentPlacement | null {
    if (!Array.isArray(item) || item[0] !== 'footprint') {
        return null;
    }

    let reference: string | undefined;
    let x: number | undefined;
    let y: number | undefined;
    let rotation: number = 0;
    let layer: string | undefined;

    for (let i = 1; i < item.length; i++) {
        const element = item[i];
        if (Array.isArray(element) && element.length > 0) {
            const key = element[0].toString().replace(/"/g, '');
            const values = element.slice(1);

            switch (key) {
                case 'layer':
                    if (typeof values[0] === 'string') {
                        layer = values[0].replace(/"/g, '');
                    }
                    break;
                case 'at':
                    if (values.length >= 2) {
                        x = parseFloat(values[0]);
                        y = parseFloat(values[1]);
                        if (values.length > 2) {
                            rotation = parseFloat(values[2]);
                        }
                        if (isNaN(x) || isNaN(y) || isNaN(rotation)) {
                            x = undefined; y = undefined; rotation = 0;
                        }
                    }
                    break;
                case 'property':
                    if (values.length > 1 && typeof values[0] === 'string' && values[0].replace(/"/g, '') === 'Reference') {
                        if (typeof values[1] === 'string') {
                            reference = values[1].replace(/"/g, '');
                        }
                    }
                    break;
            }
        }
    }

    if (reference && x !== undefined && y !== undefined && layer) {
        return { reference, x, y, rotation, layer };
    }
    return null;
}

function findFootprints(parsedData: any, depth = 0): KicadComponentPlacement[] {
    const placements: KicadComponentPlacement[] = [];
    if (Array.isArray(parsedData)) {
        for (let i = 0; i < parsedData.length; i++) {
            const item = parsedData[i];
            if (Array.isArray(item) && item.length > 0 && item[0] === 'footprint') {
                const placement = parseFootprintItem(item);
                if (placement) {
                    placements.push(placement);
                }
            } else if (Array.isArray(item)) {
                placements.push(...findFootprints(item, depth + 1));
            }
        }
    }
    return placements;
}

interface KicadVia {
    at: { x: number; y: number };
    size: number;
    drill: number;
    layers?: string[];
    net?: number;
    uuid?: string;
}

function parseViaItem(item: any[]): KicadVia | null {
    if (!Array.isArray(item) || item[0] !== 'via') {
        return null;
    }
    let at: { x: number; y: number } | undefined;
    let size: number | undefined;
    let drill: number | undefined;
    let layers: string[] | undefined;
    let net: number | undefined;
    let uuid: string | undefined;

    for (let i = 1; i < item.length; i++) {
        const element = item[i];
        if (Array.isArray(element) && element.length > 0) {
            const key = element[0].toString().replace(/"/g, '');
            const values = element.slice(1);
            switch (key) {
                case 'at':
                    if (values.length === 2) {
                        const x = parseFloat(values[0]);
                        const y = parseFloat(values[1]);
                        if (!isNaN(x) && !isNaN(y)) at = { x, y };
                    }
                    break;
                case 'size':
                    if (values.length === 1) {
                        const s = parseFloat(values[0]);
                        if (!isNaN(s)) size = s;
                    }
                    break;
                case 'drill':
                    if (values.length === 1) {
                        const d = parseFloat(values[0]);
                        if (!isNaN(d)) drill = d;
                    }
                    break;
                case 'layers':
                    if (Array.isArray(values) && values.every(v => typeof v === 'string')) {
                        layers = values.map(v => v.replace(/"/g, ''));
                    }
                    break;
                case 'net':
                    if (values.length === 1) {
                        const n = parseInt(values[0], 10);
                        if (!isNaN(n)) net = n;
                    }
                    break;
                case 'uuid':
                    if (values.length === 1 && typeof values[0] === 'string') {
                        uuid = values[0].replace(/"/g, '');
                    }
                    break;
            }
        }
    }

    if (at && size !== undefined && drill !== undefined) {
        return { at, size, drill, layers, net, uuid };
    }
    return null;
}

function findVias(parsedData: any, depth = 0): KicadVia[] {
    const vias: KicadVia[] = [];
    if (Array.isArray(parsedData)) {
        for (let i = 0; i < parsedData.length; i++) {
            const item = parsedData[i];
            if (Array.isArray(item) && item.length > 0 && item[0] === 'via') {
                const via = parseViaItem(item);
                if (via) {
                    vias.push(via);
                }
            } else if (Array.isArray(item)) {
                vias.push(...findVias(item, depth + 1));
            }
        }
    }
    return vias;
}

async function kicadDataToTypeCAD(filePath: string) { 
    let sExpressionText: string;
    const sourceDescription: string = `File: ${filePath}`;

    try {
        console.log(`Reading from ${sourceDescription}`);
        if (!fs.existsSync(filePath)) {
            console.error(`Error: File not found at ${filePath}`);
            return;
        }
        sExpressionText = fs.readFileSync(filePath, 'utf-8');
  
        if (!sExpressionText.trim()) {
            console.log(`${sourceDescription} is empty.`);
            return;
        }

        let rootElement;
        try {
            const wrappedText = `(${sExpressionText})`; 
            const parsedArray = fsexp(wrappedText);
            if (Array.isArray(parsedArray) && parsedArray.length > 0) {
                rootElement = parsedArray.pop(); 
            } else {
                throw new Error("Parser did not return a valid array structure.");
            }
        } catch (error: any) {
            console.error(`Failed to parse S-expression from ${sourceDescription}: ${error.message}`);
            return;
        }

        if (!rootElement) {
            console.error(`Parsed data from ${sourceDescription} is empty or invalid (rootElement is null/undefined).`);
            return;
        }

        const pcb = new PCB("KicadImportFromFile");

        const chKey = chalk.cyan;
        const chProp = chalk.magenta;
        const chStr = chalk.green;
        const chNum = chalk.yellow;
        const chPunc = chalk.gray;
        const chVar = chalk.blueBright; 

        const segments = findSegments(rootElement);
        if (segments.length > 0) {
            console.log(`Found ${segments.length} segments. Generating TrackBuilder chains from ${sourceDescription}.`);
            let currentTrackBuilder: TrackBuilder | null = null;
            let lastEndPoint: { x: number; y: number } | null = null;
            let currentChainLog: string = "";
            const allTrackChainLogs: string[] = [];

            segments.forEach((segment) => {
                if (!currentTrackBuilder || !lastEndPoint || lastEndPoint.x !== segment.start.x || lastEndPoint.y !== segment.start.y) {
                    if (currentTrackBuilder && currentChainLog) {
                        allTrackChainLogs.push(currentChainLog + chPunc(";"));
                    }
                    currentTrackBuilder = pcb.track().from(segment.start, segment.layer, segment.width);
                    currentChainLog = `${chVar('this.pcb')}${chPunc('.')}${chKey('track')}${chPunc('()')}${chKey('.from')}${chPunc('(')}{ ${chProp('x')}${chPunc(':')} ${chNum(segment.start.x)}${chPunc(',')} ${chProp('y')}${chPunc(':')} ${chNum(segment.start.y)} }${chPunc(',')} ${chStr('"' + segment.layer + '"')}${chPunc(',')} ${chNum(segment.width)}${chPunc(')')}`;
                    currentTrackBuilder.to({ x: segment.end.x, y: segment.end.y, layer: segment.layer, width: segment.width });
                    currentChainLog += `${chKey('.to')}${chPunc('(')}{ ${chProp('x')}${chPunc(':')} ${chNum(segment.end.x)}${chPunc(',')} ${chProp('y')}${chPunc(':')} ${chNum(segment.end.y)}${chPunc(',')} ${chProp('layer')}${chPunc(':')} ${chStr('"' + segment.layer + '"')}${chPunc(',')} ${chProp('width')}${chPunc(':')} ${chNum(segment.width)} }${chPunc(')')}`;
                } else {
                    currentTrackBuilder!.to({ x: segment.end.x, y: segment.end.y, layer: segment.layer, width: segment.width });
                    currentChainLog += `${chKey('.to')}${chPunc('(')}{ ${chProp('x')}${chPunc(':')} ${chNum(segment.end.x)}${chPunc(',')} ${chProp('y')}${chPunc(':')} ${chNum(segment.end.y)}${chPunc(',')} ${chProp('layer')}${chPunc(':')} ${chStr('"' + segment.layer + '"')}${chPunc(',')} ${chProp('width')}${chPunc(':')} ${chNum(segment.width)} }${chPunc(')')}`;
                }
                lastEndPoint = segment.end;
            });

            if (currentChainLog) {
                allTrackChainLogs.push(currentChainLog + chPunc(";"));
            }

            if (allTrackChainLogs.length > 0) {
                console.log(chalk.bold(`\n--- Generated typeCAD TrackBuilder Code from ${sourceDescription} ---`));
                allTrackChainLogs.forEach(log => console.log(log));
                console.log(chalk.bold("---------------------------------------------------------"));
            }
        } else {
            console.log(`No segments found in ${sourceDescription} content.`);
        }

        const footprints = findFootprints(rootElement);
        if (footprints.length > 0) {
            console.log(`Found ${footprints.length} footprints. Generating placement code from ${sourceDescription}.`);
            const allPlacementLogs: string[] = [];
            footprints.forEach(fp => {
                let placementLog = `${chVar('this.' + fp.reference)}${chPunc('.')}${chProp('pcb')} ${chPunc('=')} ${chPunc('{')}`;
                placementLog += ` ${chProp('x')}${chPunc(':')} ${chNum(fp.x)}${chPunc(',')}`;
                placementLog += ` ${chProp('y')}${chPunc(':')} ${chNum(fp.y)}${chPunc(',')}`;
                placementLog += ` ${chProp('rotation')}${chPunc(':')} ${chNum(fp.rotation)}`;
                placementLog += ` ${chPunc('}')}${chPunc(';')}`;
                allPlacementLogs.push(placementLog);
            });

            if (allPlacementLogs.length > 0) {
                console.log(chalk.bold(`\n--- Generated Component Placement Code from ${sourceDescription} ---`));
                allPlacementLogs.forEach(log => console.log(log));
                console.log(chalk.bold("-------------------------------------------------------------"));
            }
        } else {
            console.log(`No footprints found in ${sourceDescription} content.`);
        }

        const vias = findVias(rootElement);
        if (vias.length > 0) {
            console.log(`Found ${vias.length} vias. Generating typeCAD code from ${sourceDescription}.`);
            const allViaLogs: string[] = [];
            vias.forEach((via, index) => {
                let viaLog = `${chVar('this.v' + (index + 1))} ${chPunc('=')} ${chVar('this.pcb')}${chPunc('.')}${chKey('via')}${chPunc('({')}`;
                viaLog += ` ${chProp('at')}${chPunc(': {')} ${chProp('x')}${chPunc(':')} ${chNum(via.at.x)}${chPunc(',')} ${chProp('y')}${chPunc(':')} ${chNum(via.at.y)} ${chPunc('}')}${chPunc(',')}`;
                viaLog += ` ${chProp('size')}${chPunc(':')} ${chNum(via.size)}${chPunc(',')}`;
                viaLog += ` ${chProp('drill')}${chPunc(':')} ${chNum(via.drill)}`;
                viaLog += ` ${chPunc('})')}${chPunc(';')}`;
                allViaLogs.push(viaLog);
            });

            if (allViaLogs.length > 0) {
                console.log(chalk.bold(`\n--- Generated typeCAD Via Code from ${sourceDescription} ---`));
                allViaLogs.forEach(log => console.log(log));
                console.log(chalk.bold("-----------------------------------------------------"));
            }
        } else {
            console.log(`No vias found in ${sourceDescription} content.`);
        }

    } catch (error: any) {
        console.error(`Error processing data from ${sourceDescription}: ${error.message}`);
    }
}

const args = process.argv.slice(2);
const filePathArg = args[0];

if (filePathArg && filePathArg.trim() !== "") {
    kicadDataToTypeCAD(filePathArg);
} else {
    console.log("No KiCad file path provided. Please provide a file path as a command line argument.");
    console.log("Usage: npx tsx ./index.ts <path_to_kicad_pcb_file>");
}