import publicationLexicon from "../data/lexicons/publication.json";
import documentLexicon from "../data/lexicons/document.json";
import subscriptionLexicon from "../data/lexicons/graph.subscription.json";
import recommendLexicon from "../data/lexicons/graph.recommend.json";
import themeBasicLexicon from "../data/lexicons/theme.basic.json";
import themeColorLexicon from "../data/lexicons/theme.color.json";
import authFullLexicon from "../data/lexicons/authFull.json";
import authSocialLexicon from "../data/lexicons/authSocial.json";
import { getDescriptionOverride, getPropertyOrder } from "../data/lexicon-overrides";

export interface LexiconProperty {
  type: string;
  description?: string;
  format?: string;
  maxLength?: number;
  maxGraphemes?: number;
  maxSize?: number;
  accept?: string[];
  ref?: string;
  default?: unknown;
  items?: {
    type: string;
    maxLength?: number;
    maxGraphemes?: number;
    refs?: string[];
    closed?: boolean;
  };
}

export interface LexiconDef {
  type: string;
  description?: string;
  required?: string[];
  properties?: Record<string, LexiconProperty>;
  // For record types, the object schema is nested inside 'record'
  record?: {
    type: string;
    description?: string;
    required?: string[];
    properties?: Record<string, LexiconProperty>;
  };
}

export interface LexiconSchema {
  lexicon: number;
  id: string;
  $type: string;
  description?: string;
  defs: Record<string, LexiconDef>;
}

const lexiconCache: Record<string, LexiconSchema> = {
  "site.standard.publication": publicationLexicon as LexiconSchema,
  "site.standard.document": documentLexicon as LexiconSchema,
  "site.standard.graph.subscription": subscriptionLexicon as LexiconSchema,
  "site.standard.graph.recommend": recommendLexicon as LexiconSchema,
  "site.standard.theme.basic": themeBasicLexicon as LexiconSchema,
  "site.standard.theme.color": themeColorLexicon as LexiconSchema,
  "site.standard.authFull": authFullLexicon as LexiconSchema,
  "site.standard.authSocial": authSocialLexicon as LexiconSchema,
};

export function getLexicon(nsid: string): LexiconSchema | null {
  return lexiconCache[nsid] ?? null;
}

export function getAllLexicons() {
  const publication = getLexicon("site.standard.publication");
  const document = getLexicon("site.standard.document");

  return { publication, document };
}

export function getAllSchemas(): Record<string, LexiconSchema> {
  return { ...lexiconCache };
}

// Helper to parse lexicon properties into a display-friendly format
export interface ParsedField {
  name: string;
  type: string;
  required: boolean;
  description?: string;
  constraints: string[];
  ref?: string;      // Single ref - expandable
  refs?: string[];   // Union refs - badges only
}

// Resolve a reference to its LexiconDef
export function resolveRef(ref: string, currentSchema: LexiconSchema): LexiconDef | null {
  // Handle #localRef
  if (ref.startsWith("#")) {
    const defName = ref.slice(1);
    return currentSchema.defs[defName] ?? null;
  }

  // Handle nsid#defName (e.g., site.standard.theme.color#rgb)
  if (ref.includes("#")) {
    const [nsid, defName] = ref.split("#");
    const schema = getLexicon(nsid);
    return schema?.defs[defName] ?? null;
  }

  // Handle full NSID (e.g., site.standard.theme.basic)
  const schema = getLexicon(ref);
  return schema?.defs.main ?? null;
}

// Helper to get the object schema from a def (handles record types)
function getObjectSchema(def: LexiconDef): { properties?: Record<string, LexiconProperty>; required?: string[] } | null {
  // For record types, the object schema is nested inside 'record'
  if (def.type === 'record' && def.record) {
    return def.record;
  }
  // For object types, properties are directly on the def
  if (def.type === 'object' && def.properties) {
    return def;
  }
  return null;
}

// Parse a LexiconDef into ParsedField[]
export function parseDefFields(
  def: LexiconDef,
  lexiconId?: string,
  defName?: string | null
): ParsedField[] {
  const schema = getObjectSchema(def);
  if (!schema?.properties) {
    return [];
  }

  const requiredFields = new Set(schema.required || []);
  const propertyOrder = lexiconId ? getPropertyOrder(lexiconId, defName ?? null) : undefined;

  // Sort entries by custom order if defined
  let entries = Object.entries(schema.properties);
  if (propertyOrder) {
    entries = entries.sort((a, b) => {
      const indexA = propertyOrder.indexOf(a[0]);
      const indexB = propertyOrder.indexOf(b[0]);
      // Properties not in order list go to the end
      const orderA = indexA === -1 ? Infinity : indexA;
      const orderB = indexB === -1 ? Infinity : indexB;
      return orderA - orderB;
    });
  }

  return entries.map(([name, prop]) => {
    const constraints: string[] = [];

    if (prop.maxLength) constraints.push(`maxLength: ${prop.maxLength}`);
    if (prop.maxGraphemes) constraints.push(`maxGraphemes: ${prop.maxGraphemes}`);
    if (prop.maxSize) constraints.push(`maxSize: ${formatBytes(prop.maxSize)}`);
    if (prop.accept) constraints.push(`accept: [${prop.accept.join(", ")}]`);

    // Handle array items constraints
    if (prop.items) {
      if (prop.items.maxLength) constraints.push(`items.maxLength: ${prop.items.maxLength}`);
      if (prop.items.maxGraphemes) constraints.push(`items.maxGraphemes: ${prop.items.maxGraphemes}`);
    }

    // Build the type string
    let type = prop.type;
    if (prop.format) type = `${prop.type}:${prop.format}`;
    if (prop.ref) type = prop.ref;
    if (prop.type === "array" && prop.items) {
      type = `array<${prop.items.type}>`;
    }

    // Get refs for union types
    const refs = (prop as { refs?: string[] }).refs;

    // Check for description override
    const description = lexiconId
      ? getDescriptionOverride(lexiconId, defName ?? null, name) ?? prop.description
      : prop.description;

    return {
      name,
      type,
      required: requiredFields.has(name),
      description,
      constraints,
      ref: prop.ref,
      refs,
    };
  });
}

export function parseLexiconFields(schema: LexiconSchema): ParsedField[] {
  const mainDef = schema.defs.main;
  if (!mainDef) {
    return [];
  }
  return parseDefFields(mainDef, schema.id, "main");
}

function formatBytes(bytes: number): string {
  if (bytes >= 1_000_000) return `${bytes / 1_000_000}MB`;
  if (bytes >= 1_000) return `${bytes / 1_000}KB`;
  return `${bytes}B`;
}

// Get nested definitions (like #preferences)
export function getNestedDefs(schema: LexiconSchema): Record<string, LexiconDef> {
  const nested: Record<string, LexiconDef> = {};

  for (const [key, def] of Object.entries(schema.defs)) {
    if (key !== "main") {
      nested[key] = def;
    }
  }

  return nested;
}
