/**
 * Typed Schemas Generator
 *
 * Generates typed schema wrappers that combine raw schemas with pre-computed types.
 * This enables full type inference without runtime overhead.
 *
 * Output: A single index file that exports typed schemas ready for use.
 *
 * @example
 * ```ts
 * // Generated output
 * import { typed } from '../../speci';
 * import _atom from './schemas/sap/atom';
 * import type { LinkType } from './types/sap/atom.types';
 *
 * export const atom = typed<LinkType>(_atom);
 * ```
 */

import type {
  GeneratorPlugin,
  FinalizeContext,
  GeneratedFile,
  SchemaInfo,
} from '../codegen/types';

// ============================================================================
// Options
// ============================================================================

export interface TypedSchemasOptions {
  /** Output file path relative to package root (default: 'src/schemas/generated/index.ts') */
  outputPath?: string;
  /** Path to ts-xsd module from output file (default: 'ts-xsd') */
  tsXsdPath?: string;
  /** Path prefix to schemas from output file (default: './schemas') */
  schemasPath?: string;
  /** Path prefix to types from output file (default: './types') */
  typesPath?: string;
  /** Add file header comment */
  header?: boolean;
}

// ============================================================================
// Generator
// ============================================================================

/**
 * Create a typed schemas generator plugin
 *
 * This generator runs in finalize() after all schemas are processed.
 * It generates a single index file that exports typed schema wrappers.
 *
 * @example
 * ```ts
 * import { rawSchema, interfaces, typedSchemas } from '@abapify/ts-xsd/generators';
 *
 * export default defineConfig({
 *   generators: [
 *     rawSchema(),
 *     interfaces(),
 *     typedSchemas(),  // Generates typed wrappers
 *   ],
 * });
 * ```
 */
export function typedSchemas(
  options: TypedSchemasOptions = {},
): GeneratorPlugin {
  const {
    outputPath = 'src/schemas/generated/index.ts',
    tsXsdPath = 'ts-xsd',
    schemasPath = './schemas',
    typesPath = './types',
    header = true,
  } = options;

  return {
    name: 'typed-schemas',

    finalize(ctx: FinalizeContext): GeneratedFile[] {
      const lines: string[] = [];

      // Header
      if (header) {
        lines.push(
          '/**',
          ' * Auto-generated typed schema exports',
          ' * ',
          ' * DO NOT EDIT - Generated by ts-xsd codegen',
          ' * ',
          ' * These schemas have pre-computed types for full type inference.',
          ' * Use these instead of raw schemas for type-safe parsing/building.',
          ' * ',
          ' * @example',
          " * import { classes } from '@abapify/adt-schemas';",
          ' * const data = classes.parse(xml);  // data is fully typed!',
          ' */',
          '',
        );
      }

      // Import typedSchema helper and TypedSchema type
      lines.push(
        `import { typedSchema, type TypedSchema } from '${tsXsdPath}';`,
      );
      lines.push('');

      // Collect all schemas with their root types
      const schemaExports: Array<{
        name: string;
        sourceName: string;
        rootTypes: string[];
      }> = [];

      // Process each source
      for (const [sourceName, schemas] of ctx.processedSchemas) {
        lines.push(`// ${sourceName.toUpperCase()} schemas`);

        for (const schema of schemas) {
          const rootTypes = extractRootTypes(schema);

          if (rootTypes.length === 0) {
            // No root elements - skip or export raw
            lines.push(
              `// ${schema.name}: No root elements found, skipping typed export`,
            );
            continue;
          }

          schemaExports.push({
            name: schema.name,
            sourceName,
            rootTypes,
          });

          const exportName = toValidIdentifier(schema.name);
          const schemaImport = `${schemasPath}/${sourceName}/${schema.name}`;
          const typesImport = `${typesPath}/${sourceName}/${schema.name}.types`;

          // Import raw schema
          lines.push(`import _${exportName} from '${schemaImport}';`);

          // Import type(s)
          if (rootTypes.length === 1) {
            lines.push(
              `import type { ${rootTypes[0]} } from '${typesImport}';`,
            );
          } else {
            // Multiple root types - import all
            lines.push(
              `import type { ${rootTypes.join(', ')} } from '${typesImport}';`,
            );
          }
        }

        lines.push('');
      }

      // Generate typed exports
      lines.push(
        '// ============================================================================',
      );
      lines.push('// TYPED SCHEMA EXPORTS');
      lines.push(
        '// ============================================================================',
      );
      lines.push('');

      for (const { name, rootTypes } of schemaExports) {
        const exportName = toValidIdentifier(name);

        if (rootTypes.length === 1) {
          // Single root type - explicit type annotation for isolatedDeclarations
          lines.push(
            `export const ${exportName}: TypedSchema<${rootTypes[0]}> = typedSchema<${rootTypes[0]}>(_${exportName});`,
          );
        } else {
          // Multiple root types - use union with explicit type annotation
          const unionType = rootTypes.join(' | ');
          lines.push(
            `export const ${exportName}: TypedSchema<${unionType}> = typedSchema<${unionType}>(_${exportName});`,
          );
        }
      }

      lines.push('');

      // NOTE: Types are NOT re-exported here to avoid duplicate export conflicts.
      // Types are internal to each schema file and used for typed wrappers.
      // If you need a specific type, import it directly from the types file:
      //   import type { AbapClass } from './types/sap/classes.types';
      lines.push(
        '// Types are internal - import directly from types/{source}/{schema}.types if needed',
      );
      lines.push('');

      return [
        {
          path: outputPath,
          content: lines.join('\n'),
        },
      ];
    },
  };
}

// ============================================================================
// Helpers
// ============================================================================

/**
 * Extract root element type names from a schema
 */
function extractRootTypes(schema: SchemaInfo): string[] {
  const types: string[] = [];
  const elements = schema.schema.element;

  if (!elements || elements.length === 0) {
    return types;
  }

  for (const element of elements) {
    if (element.type) {
      // Type reference like "ns:TypeName" or "TypeName"
      const typeName = stripNamespacePrefix(element.type);
      types.push(toPascalCase(typeName));
    } else if (element.name) {
      // Anonymous type - use element name as type
      types.push(toPascalCase(element.name));
    }
  }

  return types;
}

/**
 * Strip namespace prefix from QName (e.g., "ns:TypeName" -> "TypeName")
 */
function stripNamespacePrefix(qname: string): string {
  const colonIndex = qname.indexOf(':');
  return colonIndex >= 0 ? qname.slice(colonIndex + 1) : qname;
}

/**
 * Convert to PascalCase
 */
function toPascalCase(str: string): string {
  return str
    .replaceAll(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))
    .replace(/^./, (s) => s.toUpperCase());
}

const RESERVED_WORDS = new Set([
  'break',
  'case',
  'catch',
  'continue',
  'debugger',
  'default',
  'delete',
  'do',
  'else',
  'finally',
  'for',
  'function',
  'if',
  'in',
  'instanceof',
  'new',
  'return',
  'switch',
  'this',
  'throw',
  'try',
  'typeof',
  'var',
  'void',
  'while',
  'with',
  'class',
  'const',
  'enum',
  'export',
  'extends',
  'import',
  'super',
  'implements',
  'interface',
  'let',
  'package',
  'private',
  'protected',
  'public',
  'static',
  'yield',
  'await',
  'null',
  'true',
  'false',
]);

function toValidIdentifier(name: string): string {
  // Convert hyphens to camelCase
  let result = name.replaceAll(/-([a-zA-Z])/g, (_, c) => c.toUpperCase());
  // Escape reserved words
  if (RESERVED_WORDS.has(result)) {
    result = `${result}_`;
  }
  return result;
}
