import {
  normalizeAsukaStatePayload,
  type AsukaExportedState,
} from './stateManager';

export const ASUKA_PORTABLE_PRODUCT_FORMAT = 'asuka-product' as const;
export const ASUKA_PORTABLE_PRODUCT_SCHEMA_VERSION = 1 as const;

export type AsukaPortableProductMetadata = {
  name: string;
  slug: string;
};

export type AsukaPortableProductDependencies = {
  template: string | null;
  themes: string[];
};

export type AsukaPortableProductDesigner = Record<string, unknown> & {
  /** Canonical, engine-neutral Asuka Designer state. */
  state?: AsukaExportedState;
};

export type AsukaPortableProductDocument = {
  format: typeof ASUKA_PORTABLE_PRODUCT_FORMAT;
  schemaVersion: typeof ASUKA_PORTABLE_PRODUCT_SCHEMA_VERSION;
  kind: 'product';
  metadata: AsukaPortableProductMetadata;
  designer: AsukaPortableProductDesigner;
  dependencies: AsukaPortableProductDependencies;
};

export type AsukaPortableProductCreateOptions = {
  name: string;
  slug?: string;
  state: AsukaExportedState;
  /** Additional portable designer configuration such as layout or modules. */
  designer?: Record<string, unknown>;
  dependencies?: Partial<AsukaPortableProductDependencies>;
};

const ROOT_RUNTIME_KEYS = new Set([
  'ai',
  'billing',
  'cart',
  'checkout',
  'context',
  'customers',
  'exports',
  'integrations',
  'license',
  'licensetoken',
  'orders',
  'shopify',
  'subscription',
  'woocommerce',
]);

const CONFIGURATION_RUNTIME_KEYS = new Set([
  'ai',
  'api',
  'credentials',
  'exports',
  'integrations',
  'license',
  'licensetoken',
  'token',
  'upload',
]);

const PLATFORM_KEYS = new Set(['shopify', 'woocommerce']);
const HOST_RUNTIME_KEYS = new Set([
  'billing',
  'cart',
  'checkout',
  'context',
  'customers',
  'integrations',
  'license',
  'licensetoken',
  'orders',
  'subscription',
]);
const HOST_IDENTIFIER_KEYS = new Set([
  'billingid',
  'cartid',
  'checkoutid',
  'customerid',
  'customerids',
  'lineitemid',
  'lineitemids',
  'merchandiseid',
  'orderid',
  'orderids',
  'shopdomain',
  'shopid',
  'sku',
  'skus',
  'storeid',
  'subscriptionid',
  'variantid',
  'variantids',
]);
const SECRET_KEYS = new Set([
  'accesstoken',
  'apikey',
  'authorization',
  'credentials',
  'licensekey',
  'licensetoken',
  'password',
  'refreshtoken',
  'secret',
]);
const STATE_LIKE_FIELDS = new Set([
  'contentState',
  'documentState',
  'editorState',
  'initialDocument',
]);

function asRecord(value: unknown): Record<string, unknown> | null {
  return value && typeof value === 'object' && !Array.isArray(value)
    ? (value as Record<string, unknown>)
    : null;
}

function contractKey(value: string): string {
  return value.toLowerCase().replace(/[^a-z0-9]/g, '');
}

function isHostProductId(value: unknown): boolean {
  return typeof value === 'string'
    && /^(?:gid:\/\/shopify\/|wc[:_-]|woocommerce[:_-])/i.test(value.trim());
}

function portableClone(value: unknown, path: string[] = []): unknown {
  if (value === null || typeof value === 'string' || typeof value === 'boolean') {
    return value;
  }
  if (typeof value === 'number') {
    return Number.isFinite(value) ? value : null;
  }
  if (Array.isArray(value)) {
    return value
      .map((entry) => portableClone(entry, path))
      .filter((entry) => typeof entry !== 'undefined');
  }
  const record = asRecord(value);
  if (!record) return undefined;

  const output: Record<string, unknown> = {};
  for (const [key, child] of Object.entries(record)) {
    if (typeof child === 'function' || typeof child === 'undefined') continue;
    const normalizedKey = contractKey(key);
    const parent = contractKey(path[path.length - 1] ?? '');
    const isRoot = path.length === 0;

    if (
      key.startsWith('_apd_')
      || key.startsWith('_shopify_')
      || key.startsWith('_woocommerce_')
      || normalizedKey.includes('shopify')
      || normalizedKey.includes('woocommerce')
      || PLATFORM_KEYS.has(normalizedKey)
      || HOST_RUNTIME_KEYS.has(normalizedKey)
      || HOST_IDENTIFIER_KEYS.has(normalizedKey)
      || SECRET_KEYS.has(normalizedKey)
      || (normalizedKey === 'productid' && isHostProductId(child))
      || (isRoot && ROOT_RUNTIME_KEYS.has(normalizedKey))
      || (parent === 'configuration' && CONFIGURATION_RUNTIME_KEYS.has(normalizedKey))
    ) {
      continue;
    }

    const cloned = portableClone(child, [...path, key]);
    if (typeof cloned !== 'undefined') output[key] = cloned;
  }
  return output;
}

function portableSlug(value: unknown): string {
  return String(value ?? '')
    .trim()
    .toLowerCase()
    .normalize('NFKD')
    .replace(/[\u0300-\u036f]/g, '')
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '')
    .slice(0, 160);
}

function normalizeReference(value: unknown, prefix: 'template' | 'theme'): string {
  const reference = String(value ?? '').trim();
  if (!reference) return '';
  if (reference.startsWith(`${prefix}:`)) {
    const slug = portableSlug(reference.slice(prefix.length + 1));
    return slug ? `${prefix}:${slug}` : '';
  }
  const slug = portableSlug(reference);
  return slug ? `${prefix}:${slug}` : '';
}

/**
 * Remove host, commerce, license and transport runtime data from a state while
 * preserving the complete reusable Asuka product definition.
 */
export function sanitizeAsukaPortableState(
  state: AsukaExportedState | unknown,
): AsukaExportedState {
  const normalized = normalizeAsukaStatePayload(state);
  return portableClone(normalized) as AsukaExportedState;
}

export function isAsukaPortableProductDocument(
  value: unknown,
): value is AsukaPortableProductDocument {
  const document = asRecord(value);
  return Boolean(
    document
    && document.format === ASUKA_PORTABLE_PRODUCT_FORMAT
    && document.schemaVersion === ASUKA_PORTABLE_PRODUCT_SCHEMA_VERSION
    && document.kind === 'product'
    && asRecord(document.metadata)
    && asRecord(document.designer),
  );
}

/** Create the neutral `product.json` document used by `.asuka.zip` packages. */
export function createAsukaPortableProductDocument(
  options: AsukaPortableProductCreateOptions,
): AsukaPortableProductDocument {
  const name = String(options.name ?? '').trim().slice(0, 240);
  if (!name) throw new TypeError('A portable product name is required.');

  return normalizeAsukaPortableProductDocument({
    format: ASUKA_PORTABLE_PRODUCT_FORMAT,
    schemaVersion: ASUKA_PORTABLE_PRODUCT_SCHEMA_VERSION,
    kind: 'product',
    metadata: {
      name,
      slug: portableSlug(options.slug || name),
    },
    designer: {
      ...(options.designer ?? {}),
      state: options.state,
    },
    dependencies: {
      template: options.dependencies?.template ?? null,
      themes: options.dependencies?.themes ?? [],
    },
  });
}

/** Validate and canonicalize a portable product from any Asuka integration. */
export function normalizeAsukaPortableProductDocument(
  input: unknown,
): AsukaPortableProductDocument {
  if (!isAsukaPortableProductDocument(input)) {
    throw new TypeError('Invalid portable Asuka product. Expected asuka-product schema v1.');
  }

  const source = input as unknown as Record<string, unknown>;
  const metadata = asRecord(source.metadata)!;
  const sourceDesigner = asRecord(source.designer)!;
  const sourceDependencies = asRecord(source.dependencies) ?? {};
  const name = String(metadata.name ?? '').trim().slice(0, 240);
  if (!name) throw new TypeError('The portable Asuka product has no name.');

  const clonedDesigner = (portableClone(sourceDesigner) ?? {}) as Record<string, unknown>;
  const legacyState = clonedDesigner.initialState;
  const stateCandidate = clonedDesigner.state ?? legacyState;
  delete clonedDesigner.initialState;
  if (stateCandidate !== undefined && stateCandidate !== null) {
    clonedDesigner.state = sanitizeAsukaPortableState(stateCandidate);
  }
  for (const field of STATE_LIKE_FIELDS) {
    if (sourceDesigner[field] !== undefined) {
      clonedDesigner[field] = portableClone(sourceDesigner[field], []);
    }
  }
  const upload = asRecord(clonedDesigner.upload);
  if (upload) {
    for (const key of ['endpoint', 'headers', 'credentials', 'token', 'apiKey', 'licenseToken']) {
      delete upload[key];
    }
    const strategy = String(upload.strategy ?? '').toLowerCase();
    if (['remote', 'server', 'blob'].includes(strategy)) {
      for (const key of ['strategy', 'method', 'fieldName', 'source']) delete upload[key];
    }
    if (Object.keys(upload).length === 0) delete clonedDesigner.upload;
  }

  const template = normalizeReference(sourceDependencies.template, 'template');
  const themes = Array.isArray(sourceDependencies.themes)
    ? [...new Set(sourceDependencies.themes
      .map((theme) => normalizeReference(theme, 'theme'))
      .filter(Boolean))]
    : [];

  return {
    format: ASUKA_PORTABLE_PRODUCT_FORMAT,
    schemaVersion: ASUKA_PORTABLE_PRODUCT_SCHEMA_VERSION,
    kind: 'product',
    metadata: {
      name,
      slug: portableSlug(metadata.slug || name),
    },
    designer: clonedDesigner as AsukaPortableProductDesigner,
    dependencies: {
      template: template || null,
      themes,
    },
  };
}

/** Return the canonical state embedded in a portable product. */
export function extractAsukaPortableProductState(
  document: AsukaPortableProductDocument | unknown,
): AsukaExportedState {
  const normalized = normalizeAsukaPortableProductDocument(document);
  if (!normalized.designer.state) {
    throw new TypeError('The portable Asuka product does not contain a designer state.');
  }
  return sanitizeAsukaPortableState(normalized.designer.state);
}
