#!/usr/bin/env node
// Build tokens DTCG → CSS / JS / TS / Penpot-ready JSON.
// Zero-dependency: lê os JSONs DTCG e gera artefatos derivados.
// (Alternativa: usar style-dictionary; mantemos vanilla para evitar bloat.)

import { readFile, writeFile, mkdir, readdir } from 'node:fs/promises';
import { resolve, dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const DTCG_DIR = resolve(__dirname, 'dtcg');
const OUT_DIR = resolve(__dirname, 'src');

// --- helpers ---
const isToken = obj => obj && typeof obj === 'object' && '$value' in obj;

async function loadAll() {
  const merged = {};
  const sets = [];
  async function walk(dir) {
    const entries = await readdir(dir, { withFileTypes: true });
    for (const e of entries) {
      const p = join(dir, e.name);
      if (e.isDirectory()) await walk(p);
      else if (e.name.endsWith('.json') && !e.name.startsWith('$')) {
        const obj = JSON.parse(await readFile(p, 'utf8'));
        sets.push(p);
        deepMerge(merged, obj);
      }
    }
  }
  await walk(DTCG_DIR);
  return { merged, sets };
}

function deepMerge(a, b) {
  for (const k in b) {
    if (b[k] && typeof b[k] === 'object' && !Array.isArray(b[k]) && !isToken(b[k]) && a[k]) deepMerge(a[k], b[k]);
    else a[k] = b[k];
  }
}

// Resolve aliases: "{color.bordo}" → real value
function resolveAliases(tokens) {
  const flat = flatten(tokens);
  const lookup = (path) => {
    const t = flat[path];
    if (!t) return undefined;
    const v = t.$value;
    if (typeof v === 'string' && v.startsWith('{') && v.endsWith('}')) {
      return lookup(v.slice(1, -1));
    }
    return v;
  };
  const resolved = JSON.parse(JSON.stringify(tokens));
  walkResolve(resolved, lookup);
  return resolved;
}

function flatten(obj, prefix = '', out = {}, inheritedType = null) {
  // Inherit $type from parent group (DTCG allows declaring once on a group).
  const groupType = obj.$type || inheritedType;
  for (const k in obj) {
    if (k.startsWith('$')) continue;
    const path = prefix ? `${prefix}.${k}` : k;
    if (isToken(obj[k])) {
      // Token may not declare its own $type; inherit from group.
      out[path] = obj[k].$type ? obj[k] : { ...obj[k], $type: groupType };
    } else if (typeof obj[k] === 'object') {
      flatten(obj[k], path, out, groupType);
    }
  }
  return out;
}

function walkResolve(node, lookup) {
  for (const k in node) {
    if (k.startsWith('$')) continue;
    if (isToken(node[k])) {
      let v = node[k].$value;
      if (typeof v === 'string' && v.includes('{')) {
        node[k].$value = v.replace(/\{([^}]+)\}/g, (_, p) => lookup(p) ?? `{${p}}`);
      } else if (v && typeof v === 'object') {
        for (const sk in v) {
          if (typeof v[sk] === 'string' && v[sk].includes('{')) {
            v[sk] = v[sk].replace(/\{([^}]+)\}/g, (_, p) => lookup(p) ?? `{${p}}`);
          }
        }
      }
    } else if (typeof node[k] === 'object') {
      walkResolve(node[k], lookup);
    }
  }
}

// --- emitters ---
function emitCSS(flat) {
  const lines = [':root {'];
  for (const [path, t] of Object.entries(flat)) {
    if (t.$type === 'color' || t.$type === 'dimension' || t.$type === 'number' || t.$type === 'fontWeight') {
      lines.push(`  --${path.replace(/\./g, '-')}: ${t.$value};`);
    } else if (t.$type === 'fontFamily') {
      lines.push(`  --${path.replace(/\./g, '-')}: ${t.$value};`);
    }
  }
  lines.push('}');
  return lines.join('\n') + '\n';
}

function emitJS(flat) {
  const out = {};
  for (const [path, t] of Object.entries(flat)) {
    setDeep(out, path.split('.'), t.$value);
  }
  return `// Generated by packages/tokens/build.js — do not edit.
export const tokens = ${JSON.stringify(out, null, 2)};
export default tokens;
`;
}

function emitDTS(flat) {
  const out = {};
  for (const path of Object.keys(flat)) setDeep(out, path.split('.'), null);
  function tsType(obj) {
    if (obj === null) return 'string | number';
    const entries = Object.entries(obj).map(([k, v]) => `  ${JSON.stringify(k)}: ${tsType(v)};`);
    return `{\n${entries.join('\n')}\n}`;
  }
  return `// Generated by packages/tokens/build.js — do not edit.
export declare const tokens: ${tsType(out)};
export default tokens;
`;
}

function setDeep(obj, path, value) {
  let cur = obj;
  for (let i = 0; i < path.length - 1; i++) {
    cur[path[i]] ??= {};
    cur = cur[path[i]];
  }
  cur[path[path.length - 1]] = value;
}

// Penpot-ready DTCG (single resolved JSON)
function emitPenpotJSON(resolved) {
  return JSON.stringify(resolved, null, 2);
}

// --- main ---
async function main() {
  await mkdir(OUT_DIR, { recursive: true });
  const { merged, sets } = await loadAll();
  const resolved = resolveAliases(merged);
  const flat = flatten(resolved);

  await writeFile(resolve(OUT_DIR, 'tokens.css'), emitCSS(flat));
  await writeFile(resolve(OUT_DIR, 'tokens.js'),  emitJS(flat));
  await writeFile(resolve(OUT_DIR, 'tokens.d.ts'), emitDTS(flat));
  await writeFile(resolve(OUT_DIR, 'tokens.penpot.json'), emitPenpotJSON(resolved));

  console.log(`✓ Built ${Object.keys(flat).length} tokens from ${sets.length} sets → packages/tokens/src/`);
}

main().catch(err => { console.error(err); process.exit(1); });
