import fs from 'node:fs'
import path from 'node:path'
import crypto from 'node:crypto'

const sourcePath = 'contracts/fields/kbx.fields.json'
const source = JSON.parse(fs.readFileSync(sourcePath, 'utf8'))
const fields = [...source.fields].sort((a, b) => a.key.localeCompare(b.key))
const hash = crypto.createHash('sha256').update(JSON.stringify(source)).digest('hex')

const tsDir = 'packages/kbx-contracts/src/generated'
const csDir = 'backend/Shared/Contracts/Generated'
fs.mkdirSync(tsDir, { recursive: true })
fs.mkdirSync(csDir, { recursive: true })
fs.mkdirSync('generated', { recursive: true })

const q = value => JSON.stringify(value)
const ts = [
  '// GENERATED FILE. DO NOT EDIT.',
  `// Source: ${sourcePath}`,
  `// Source SHA256: ${hash}`,
  '',
  `export const kbxFieldDictionaryVersion = ${q(source.version)} as const`,
  '',
  'export const kbxFieldCatalog = {',
  ...fields.map(f => `  ${q(f.key)}: ${JSON.stringify(f)},`),
  '} as const',
  '',
  'export type KbxKnownFieldKey = keyof typeof kbxFieldCatalog',
  'export type KbxKnownFieldDefinition = (typeof kbxFieldCatalog)[KbxKnownFieldKey]',
  '',
  'export function getKbxField<K extends KbxKnownFieldKey>(key: K): (typeof kbxFieldCatalog)[K] {',
  '  return kbxFieldCatalog[key]',
  '}',
  '',
].join('\n')
fs.writeFileSync(path.join(tsDir, 'fieldCatalog.ts'), ts)

const csString = value => value == null ? 'null' : `"${String(value).replaceAll('\\','\\\\').replaceAll('"','\\"')}"`
const csBool = value => value ? 'true' : 'false'
const pascal = s => s[0].toUpperCase() + s.slice(1)
const cs = [
  '// <auto-generated />',
  `// Source: ${sourcePath}`,
  `// Source SHA256: ${hash}`,
  'namespace Kbx.Contracts.Generated;',
  '',
  'public sealed record KbxFieldMetadata(',
  '    string Key, string Label, string DataType, IReadOnlyList<string> Aliases,',
  '    bool Required, int? MaxLength, int? Precision, int? Scale, string? LookupEntity,',
  '    bool Readonly, bool Importable, bool Exportable, bool Sensitive, string? Masking);',
  '',
  'public static class KbxFieldKeys',
  '{',
  ...fields.map(f => `    public const string ${pascal(f.key)} = "${f.key}";`),
  '}',
  '',
  'public static class KbxFieldCatalog',
  '{',
  `    public const string Version = "${source.version}";`,
  '    private static readonly IReadOnlyDictionary<string, KbxFieldMetadata> Items =',
  '        new Dictionary<string, KbxFieldMetadata>(StringComparer.Ordinal)',
  '        {',
  ...fields.map(f => {
    const aliases = (f.aliases ?? []).map(csString).join(', ')
    return `            ["${f.key}"] = new("${f.key}", ${csString(f.label)}, "${f.dataType}", new[] { ${aliases} }, ${csBool(Boolean(f.required))}, ${f.maxLength ?? 'null'}, ${f.precision ?? 'null'}, ${f.scale ?? 'null'}, ${csString(f.lookupEntity)}, ${csBool(Boolean(f.readonly))}, ${csBool(Boolean(f.importable))}, ${csBool(Boolean(f.exportable))}, ${csBool(Boolean(f.sensitive))}, ${csString(f.masking)}),`
  }),
  '        };',
  '',
  '    public static KbxFieldMetadata Get(string key) => Items.TryGetValue(key, out var value)',
  '        ? value',
  '        : throw new KeyNotFoundException($"Unknown KBX field key: {key}");',
  '',
  '    public static bool TryGet(string key, out KbxFieldMetadata? value) => Items.TryGetValue(key, out value);',
  '    public static IReadOnlyCollection<KbxFieldMetadata> All => Items.Values;',
  '}',
  '',
].join('\n')
fs.writeFileSync(path.join(csDir, 'KbxFieldCatalog.g.cs'), cs)

const manifest = {
  version: source.version,
  source: sourcePath,
  sourceSha256: hash,
  count: fields.length,
  fields,
}
fs.writeFileSync('generated/field-manifest.json', JSON.stringify(manifest, null, 2) + '\n')
console.log(`field contracts generated: ${fields.length} fields, version ${source.version}`)
