import { mkdir, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
import { join } from 'node:path';

const componentsDir = process.env.HA_CORE_COMPONENTS_DIR || '/tmp/opencode/homeassistant-core/homeassistant/components';
const integrationsRoot = new URL('../ts/integrations/', import.meta.url);
const generatedRoot = new URL('../ts/integrations/generated/', import.meta.url);
const markerName = '.generated-by-smarthome-exchange';

const toClassName = (domain) => {
  const parts = domain.split(/[^a-zA-Z0-9]+/).filter(Boolean);
  const pascal = parts.map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`).join('') || 'Unknown';
  return `HomeAssistant${pascal}Integration`;
};

const readManifest = async (componentDir, fallbackDomain) => {
  try {
    return JSON.parse(await readFile(join(componentDir, 'manifest.json'), 'utf8'));
  } catch {
    return { domain: fallbackDomain, name: fallbackDomain };
  }
};

const isGeneratedFolder = async (folderUrl) => {
  try {
    await stat(new URL(`${markerName}`, folderUrl));
    return true;
  } catch {
    return false;
  }
};

const fileExists = async (fileUrl) => {
  try {
    await stat(fileUrl);
    return true;
  } catch {
    return false;
  }
};

const json = (value) => JSON.stringify(value, null, 2);

await mkdir(integrationsRoot, { recursive: true });
await mkdir(generatedRoot, { recursive: true });

for (const entry of await readdir(integrationsRoot, { withFileTypes: true })) {
  if (!entry.isDirectory()) continue;
  if (entry.name === 'generated') continue;
  const folderUrl = new URL(`./${entry.name}/`, integrationsRoot);
  if (await isGeneratedFolder(folderUrl)) {
    await rm(folderUrl, { recursive: true, force: true });
  }
}

const ports = [];

for (const entry of await readdir(componentsDir, { withFileTypes: true })) {
  if (!entry.isDirectory()) continue;
  if (entry.name.startsWith('__')) continue;

  const componentDir = join(componentsDir, entry.name);
  const manifest = await readManifest(componentDir, entry.name);
  const domain = String(manifest.domain || entry.name);
  const folderName = domain.replace(/[^a-z0-9_]/gi, '_').toLowerCase();
  const folderUrl = new URL(`./${folderName}/`, integrationsRoot);
  const className = toClassName(domain);
  const metadata = {
    source: 'home-assistant/core',
    upstreamPath: `homeassistant/components/${entry.name}`,
    upstreamDomain: domain,
    integrationType: manifest.integration_type ? String(manifest.integration_type) : undefined,
    iotClass: manifest.iot_class ? String(manifest.iot_class) : undefined,
    qualityScale: manifest.quality_scale ? String(manifest.quality_scale) : undefined,
    requirements: Array.isArray(manifest.requirements) ? manifest.requirements.map(String) : [],
    dependencies: Array.isArray(manifest.dependencies) ? manifest.dependencies.map(String) : [],
    afterDependencies: Array.isArray(manifest.after_dependencies) ? manifest.after_dependencies.map(String) : [],
    codeowners: Array.isArray(manifest.codeowners) ? manifest.codeowners.map(String) : [],
  };

  let handwritten = false;
  try {
    await stat(folderUrl);
    handwritten = !(await isGeneratedFolder(folderUrl));
  } catch {}

  if (!handwritten) {
    await mkdir(folderUrl, { recursive: true });
    await writeFile(new URL(markerName, folderUrl), 'This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.\n');
    await writeFile(
      new URL('index.ts', folderUrl),
      `export * from './${folderName}.classes.integration.js';\nexport * from './${folderName}.types.js';\n`
    );
    await writeFile(
      new URL(`${folderName}.types.ts`, folderUrl),
      `export interface I${className.replace(/Integration$/, 'Config')} {\n  // TODO: replace with the TypeScript-native config for ${domain}.\n  [key: string]: unknown;\n}\n`
    );
    await writeFile(
      new URL(`${folderName}.classes.integration.ts`, folderUrl),
      `import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';\n\nexport class ${className} extends DescriptorOnlyIntegration {\n  constructor() {\n    super({\n      domain: ${JSON.stringify(domain)},\n      displayName: ${JSON.stringify(manifest.name ? String(manifest.name) : domain)},\n      status: 'descriptor-only',\n      metadata: ${json(metadata)},\n    });\n  }\n}\n`
    );
  }

  ports.push({
    domain,
    folderName,
    className,
    handwritten,
  });
}

ports.sort((a, b) => a.domain.localeCompare(b.domain));

const imports = ports
  .filter((port) => !port.handwritten)
  .map((port) => `import { ${port.className} } from '../${port.folderName}/index.js';`)
  .join('\n');

const constructorPushes = ports
  .filter((port) => !port.handwritten)
  .map((port) => `generatedHomeAssistantPortIntegrations.push(new ${port.className}());`)
  .join('\n');

await writeFile(
  new URL('index.ts', generatedRoot),
  `// Generated by scripts/generate-homeassistant-ports.mjs. Do not edit manually.\n\nimport type { BaseIntegration } from '../../core/classes.baseintegration.js';\n${imports}\n\nexport const generatedHomeAssistantPortIntegrations: BaseIntegration[] = [];\n${constructorPushes}\n\nexport const generatedHomeAssistantPortCount = ${ports.filter((port) => !port.handwritten).length};\nexport const handwrittenHomeAssistantPortDomains = ${json(ports.filter((port) => port.handwritten).map((port) => port.domain))};\n`
);

const handwrittenFolders = [];
for (const entry of await readdir(integrationsRoot, { withFileTypes: true })) {
  if (!entry.isDirectory()) continue;
  if (entry.name === 'generated') continue;

  const folderUrl = new URL(`./${entry.name}/`, integrationsRoot);
  if (await isGeneratedFolder(folderUrl)) continue;
  if (!(await fileExists(new URL('index.ts', folderUrl)))) continue;

  handwrittenFolders.push(entry.name);
}

handwrittenFolders.sort((a, b) => a.localeCompare(b));

await writeFile(
  new URL('index.ts', integrationsRoot),
  [
    '// Generated by scripts/generate-homeassistant-ports.mjs. Do not edit manually.',
    "export * from './generated/index.js';",
    ...handwrittenFolders.map((folderName) => `export * from './${folderName}/index.js';`),
    '',
  ].join('\n')
);

console.log(`Generated ${ports.filter((port) => !port.handwritten).length} native TypeScript port skeletons. Preserved ${ports.filter((port) => port.handwritten).length} handwritten folders.`);
