import SwaggerParser from "@apidevtools/swagger-parser";
import { AppConfig, ResourceConfig, ResourceField, FieldType } from "../types/config";
import { configuration, profileConfiguration } from "../configuration";

/**
 * Maps OpenAPI property types to our internal FieldType
 */
function mapOpenApiType(prop: any): FieldType {
  const type = prop.type;
  const format = prop.format;

  if (format === "date-time") return "datetime";
  if (format === "date") return "date";
  if (prop.enum) return "enum";
  if (
    type === "string" &&
    (prop.description?.toLowerCase().includes("image") ||
      prop.name?.toLowerCase().includes("icon"))
  )
    return "image";

  switch (type) {
    case "integer":
    case "number":
      return "number";
    case "boolean":
      return "boolean";
    case "object":
      return "object";
    case "array":
      return "array";
    default:
      return "string";
  }
}

/**
 * Recursively converts OpenAPI schemas to ResourceField map
 */
function parseSchemaFields(
  schema: any,
  resourceName: string,
  schemaToResourceMap: Map<any, string>
): Record<string, ResourceField> {
  const fields: Record<string, ResourceField> = {};
  const properties = schema.properties || {};
  const required = schema.required || [];
  const overrides = configuration[resourceName]?.fields || {};

  for (const [key, prop] of Object.entries(properties) as [string, any]) {
    const type = mapOpenApiType(prop);
    const override = overrides[key];

    // Explicitly skip 'id' as it's the primary key and handled elsewhere
    if (key === "id" || override?.display === false) continue;

    fields[key] = {
      type,
      label:
        prop.title ||
        key.charAt(0).toUpperCase() + key.slice(1).replace(/_/g, " "),
      required: required.includes(key),
      options: prop.enum,
      readOnly:
        prop.readOnly ||
        key === "created_at" ||
        key === "updated_at",
      ...override,
    };

    // STRICT RELATION DETECTION
    // A field is a relation ONLY if its schema object (or items schema) 
    // exactly matches a schema that is defined as a resource.
    let targetSchema = prop;
    if (type === "array" && prop.items) {
      targetSchema = prop.items;
    }

    // Check if this schema object is registered as a resource
    const relation = schemaToResourceMap.get(targetSchema);
    if (relation) {
      fields[key].relation = relation;
    }

    // Recursively parse nested objects (only if not a relation)
    if (fields[key].type === "object" && prop.properties && !relation) {
      fields[key].schema = parseSchemaFields(prop, resourceName, schemaToResourceMap);
    }
  }

  return fields;
}

/**
 * Scans paths to identify resources and their basic configuration
 */
export async function loadConfigFromOpenApi(baseUrl: string): Promise<AppConfig> {
  // Use SwaggerParser to dereference the spec.
  // Dereferencing preserves object identity for $ref targets.
  const api = await SwaggerParser.dereference(
    new URL("/openapi.json", baseUrl).href
  );

  const resources: ResourceConfig[] = [];
  const paths = api.paths || {};

  // Group paths by base resource name
  const resourcePaths: Record<string, any> = {};
  for (const path of Object.keys(paths)) {
    const base = path.split("/")[1];
    if (!base) continue;

    if (!resourcePaths[base]) resourcePaths[base] = { path, methods: [] };
    const methods = Object.keys(paths[path] || {});
    resourcePaths[base].methods.push(...methods);

    // Identify the list endpoint for this resource
    if (!resourcePaths[base].listPath && !path.includes("{") && paths[path]?.get?.responses?.["200"]) {
      resourcePaths[base].listPath = path;
    }
  }

  // 1. Identify which schema objects correspond to which resources
  const schemaToResourceMap = new Map<any, string>();
  for (const [name, info] of Object.entries(resourcePaths)) {
    const listPath = info.listPath || `/${name}`;
    const listOp = paths[listPath]?.get;
    if (!listOp) continue;

    // @ts-ignore
    const responseSchema = listOp.responses?.["200"]?.content?.["application/json"]?.schema;
    let schemaObj = responseSchema;
    if (responseSchema?.type === "array" && responseSchema.items) {
      schemaObj = responseSchema.items;
    }

    if (schemaObj) {
      schemaToResourceMap.set(schemaObj, name);
      resourcePaths[name].schemaObj = schemaObj;
    }
  }

  // 2. Generate ResourceConfig for each identified resource
  for (const [name, info] of Object.entries(resourcePaths)) {
    const listPath = info.listPath || `/${name}`;
    const listOp = paths[listPath]?.get;
    if (!listOp || !info.schemaObj) continue;

    const schema = info.schemaObj;
    const label = name.charAt(0).toUpperCase() + name.slice(1, -1);
    const pluralLabel = name.charAt(0).toUpperCase() + name.slice(1);

    const fields = parseSchemaFields(schema, name, schemaToResourceMap);

    resources.push({
      name,
      label: schema.title || label,
      pluralLabel: pluralLabel,
      endpoint: listPath,
      primaryKey: "id", // Strict default, no heuristics
      fields,
    });
  }

  // @ts-ignore
  const serverBaseUrl = import.meta.env.VITE_API_BASE_URL || (api.servers?.[0]?.url ?? "")
  // @ts-ignore
  const authBaseUrl = import.meta.env.VITE_AUTH_BASE_URL || ""
  return {
    baseUrl: serverBaseUrl,
    authBaseUrl: authBaseUrl,
    resources,
    profile: profileConfiguration,
  };
}
