import fs from 'node:fs/promises';
import * as acorn from 'acorn';
import jsx from 'acorn-jsx';

const JSD_STRING = /\(\s*(<.*)>\s*\)/gs;

const parser = acorn.Parser.extend(jsx());

export async function parseJSDFile(filename: string) {
  const content = (await fs.readFile(filename)).toString();

  const matches = JSD_STRING.exec(content);
  if (matches) {
    const jsxc = matches[1] + '>';
    const ast = parser.parse(jsxc, { ecmaVersion: 2020, sourceType: 'module' });
    const translated = traverseJSX((ast.body[0] as any).expression);
    const str = content.replace(matches[1] + '>', translated);
    await fs.writeFile(filename.replace('.tsd', '.ts'), str);
  }
  return true;
}

function traverseJSX(node: any): string {
  if (node.type === 'JSXElement') {
    const tag = node.openingElement.name.name;
    const attrs: Record<string, any> = {};
    for (const attr of node.openingElement.attributes) {
      if (attr.type === 'JSXAttribute') {
        const name = attr.name.name;
        const value = attr.value;
        if (value.type === 'Literal') {
          attrs[name] = value.value;
        } else if (value.type === 'JSXExpressionContainer') {
          attrs[name] = `{${generateCode(value.expression)}}`;
        } else if (value) {
          attrs[name] = value.raw;
        }
      }
    }
    const children = [];
    for (const child of node.children) {
      const translated = traverseJSX(child);
      if (translated) {
        children.push(translated);
      }
    }
    return `StarKitten.createElement("${tag}", ${JSON.stringify(attrs)}${children.length > 0 ? ', ' + children.join(', ') : ''})`;
  } else if (node.type === 'JSXExpressionContainer') {
    const expr = generateCode(node.expression);
    if (node.expression.type === 'TemplateLiteral' || (node.expression.type === 'Literal' && typeof node.expression.value === 'string')) {
      return `""+ ${expr} +""`;
    } else {
      return expr;
    }
  } else if (node.type === 'JSXText') {
    const text = node.value.trim();
    if (text) {
      return `"${text}"`;
    }
  }
  return '';
}

function generateCode(node: any): string {
  if (node.type === 'JSXElement') {
    return traverseJSX(node);
  } else if (node.type === 'Identifier') {
    return node.name;
  } else if (node.type === 'Literal') {
    return JSON.stringify(node.value);
  } else if (node.type === 'TemplateLiteral') {
    const quasis = node.quasis.map((q: any) => q.value.raw);
    const expressions = node.expressions.map((e: any) => generateCode(e));
    let result = quasis[0];
    for (let i = 0; i < expressions.length; i++) {
      result += '${' + expressions[i] + '}' + quasis[i + 1];
    }
    return '`' + result + '`';
  } else if (node.type === 'MemberExpression') {
    const op = node.optional ? '?.' : '.';
    return generateCode(node.object) + op + (node.computed ? '[' + generateCode(node.property) + ']' : generateCode(node.property));
  } else if (node.type === 'OptionalMemberExpression') {
    return generateCode(node.object) + '?.' + (node.computed ? '[' + generateCode(node.property) + ']' : generateCode(node.property));
  } else if (node.type === 'CallExpression') {
    return generateCode(node.callee) + '(' + node.arguments.map((a: any) => generateCode(a)).join(', ') + ')';
  } else if (node.type === 'BinaryExpression') {
    return generateCode(node.left) + ' ' + node.operator + ' ' + generateCode(node.right);
  } else if (node.type === 'ConditionalExpression') {
    return generateCode(node.test) + ' ? ' + generateCode(node.consequent) + ' : ' + generateCode(node.alternate);
  } else if (node.type === 'LogicalExpression') {
    return generateCode(node.left) + ' ' + node.operator + ' ' + generateCode(node.right);
  } else if (node.type === 'UnaryExpression') {
    return node.operator + generateCode(node.argument);
  } else if (node.type === 'ObjectExpression') {
    return '{' + node.properties.map((p: any) => generateCode(p.key) + ': ' + generateCode(p.value)).join(', ') + '}';
  } else if (node.type === 'ArrayExpression') {
    return '[' + node.elements.map((e: any) => generateCode(e)).join(', ') + ']';
  } else {
    return node.raw || node.name || 'unknown';
  }
}
