/**
 * Static provider package metadata declared in package.json under
 * `beignet.provider`.
 *
 * This metadata is for docs and tooling. It must stay side-effect-free and
 * does not change provider setup, ordering, or runtime port merging behavior.
 */

export type ProviderPackagePortMetadata = {
  name: string;
  type: string;
};

export type ProviderPackageRegistrationSeverity = "warning" | "hint";

export type ProviderPackageRegistrationMetadata = {
  required?: boolean;
  severity?: ProviderPackageRegistrationSeverity;
  tokens?: readonly string[];
};

export type ProviderPackageVariantMetadata = {
  name: string;
  displayName?: string;
  env?: readonly string[];
  requiredEnv?: readonly string[];
  requiredEnvAlternatives?: readonly (readonly string[])[];
  requiredTables?: readonly string[];
  registration?: ProviderPackageRegistrationMetadata;
};

export type ProviderPackageMetadata = {
  displayName?: string;
  ports?: readonly string[];
  appPorts?: readonly ProviderPackagePortMetadata[];
  env?: readonly string[];
  requiredEnv?: readonly string[];
  requiredEnvAlternatives?: readonly (readonly string[])[];
  requiredTables?: readonly string[];
  registration?: ProviderPackageRegistrationMetadata;
  variants?: readonly ProviderPackageVariantMetadata[];
  watchers?: readonly string[];
};

export type ProviderPackageMetadataIssue = {
  path: string;
  message: string;
};

export type ProviderPackageMetadataParseResult =
  | { success: true; metadata: ProviderPackageMetadata }
  | { success: false; issues: readonly ProviderPackageMetadataIssue[] };

export function parseProviderPackageMetadata(
  input: unknown,
): ProviderPackageMetadataParseResult {
  if (!isRecord(input)) {
    return {
      success: false,
      issues: [
        {
          path: "beignet.provider",
          message: "must be an object",
        },
      ],
    };
  }

  const issues: ProviderPackageMetadataIssue[] = [];
  const metadata: ProviderPackageMetadata = {};

  if (input.displayName !== undefined) {
    if (typeof input.displayName !== "string") {
      issues.push({
        path: "beignet.provider.displayName",
        message: "must be a string",
      });
    } else {
      metadata.displayName = input.displayName;
    }
  }

  const ports = parseStringArray(input.ports, "beignet.provider.ports", issues);
  if (ports !== undefined) metadata.ports = ports;

  const env = parseStringArray(input.env, "beignet.provider.env", issues);
  if (env !== undefined) metadata.env = env;

  const requiredEnv = parseStringArray(
    input.requiredEnv,
    "beignet.provider.requiredEnv",
    issues,
  );
  if (requiredEnv !== undefined) {
    metadata.requiredEnv = requiredEnv;
    validateRequiredEnvMembers(
      requiredEnv,
      env,
      "beignet.provider.requiredEnv",
      "beignet.provider.env",
      issues,
    );
  }

  const requiredEnvAlternatives = parseStringArrayArray(
    input.requiredEnvAlternatives,
    "beignet.provider.requiredEnvAlternatives",
    issues,
  );
  if (requiredEnvAlternatives !== undefined) {
    metadata.requiredEnvAlternatives = requiredEnvAlternatives;
    for (const alternative of requiredEnvAlternatives) {
      validateRequiredEnvMembers(
        alternative,
        env,
        "beignet.provider.requiredEnvAlternatives",
        "beignet.provider.env",
        issues,
      );
    }
  }

  if (
    input.requiredEnv !== undefined &&
    input.requiredEnvAlternatives !== undefined
  ) {
    issues.push({
      path: "beignet.provider.requiredEnvAlternatives",
      message: "must not be set alongside beignet.provider.requiredEnv",
    });
  }

  const requiredTables = parseStringArray(
    input.requiredTables,
    "beignet.provider.requiredTables",
    issues,
  );
  if (requiredTables !== undefined) metadata.requiredTables = requiredTables;

  const watchers = parseStringArray(
    input.watchers,
    "beignet.provider.watchers",
    issues,
  );
  if (watchers !== undefined) metadata.watchers = watchers;

  const appPorts = parseAppPorts(input.appPorts, issues);
  if (appPorts !== undefined) metadata.appPorts = appPorts;

  const registration = parseRegistration(
    input.registration,
    issues,
    "beignet.provider.registration",
  );
  if (registration !== undefined) metadata.registration = registration;

  const variants = parseVariants(input.variants, issues);
  if (variants !== undefined) metadata.variants = variants;

  if (input.variants !== undefined) {
    if (input.requiredEnv !== undefined) {
      issues.push({
        path: "beignet.provider.requiredEnv",
        message:
          "must not be set when beignet.provider.variants is present; declare requiredEnv on each variant instead",
      });
    }
    if (input.requiredEnvAlternatives !== undefined) {
      issues.push({
        path: "beignet.provider.requiredEnvAlternatives",
        message:
          "must not be set when beignet.provider.variants is present; declare requiredEnvAlternatives on each variant instead",
      });
    }
    if (input.registration !== undefined) {
      issues.push({
        path: "beignet.provider.registration",
        message:
          "must not be set when beignet.provider.variants is present; declare registration on each variant instead",
      });
    }
    if (input.requiredTables !== undefined) {
      issues.push({
        path: "beignet.provider.requiredTables",
        message:
          "must not be set when beignet.provider.variants is present; declare requiredTables on each variant instead",
      });
    }
  }

  if (issues.length > 0) {
    return { success: false, issues };
  }

  return { success: true, metadata };
}

function parseStringArray(
  value: unknown,
  path: string,
  issues: ProviderPackageMetadataIssue[],
): string[] | undefined {
  if (value === undefined) return undefined;
  if (!Array.isArray(value)) {
    issues.push({ path, message: "must be an array of strings" });
    return undefined;
  }

  const parsed: string[] = [];
  for (const [index, entry] of value.entries()) {
    if (typeof entry !== "string") {
      issues.push({
        path: `${path}[${index}]`,
        message: "must be a string",
      });
      continue;
    }
    parsed.push(entry);
  }

  return parsed;
}

function parseStringArrayArray(
  value: unknown,
  path: string,
  issues: ProviderPackageMetadataIssue[],
): string[][] | undefined {
  if (value === undefined) return undefined;
  if (!Array.isArray(value)) {
    issues.push({ path, message: "must be an array of string arrays" });
    return undefined;
  }
  if (value.length === 0) {
    issues.push({
      path,
      message: "must include at least one alternative when present",
    });
    return undefined;
  }

  const parsed: string[][] = [];
  for (const [index, entry] of value.entries()) {
    const entryPath = `${path}[${index}]`;
    const alternative = parseStringArray(entry, entryPath, issues);
    if (alternative === undefined) continue;
    if (alternative.length === 0) {
      issues.push({
        path: entryPath,
        message: "must include at least one environment variable",
      });
      continue;
    }
    parsed.push(alternative);
  }

  return parsed;
}

function validateRequiredEnvMembers(
  requiredEnv: readonly string[],
  env: readonly string[] | undefined,
  path: string,
  envPath: string,
  issues: ProviderPackageMetadataIssue[],
): void {
  const envSet = new Set(env ?? []);
  for (const envVar of requiredEnv) {
    if (!envSet.has(envVar)) {
      issues.push({
        path,
        message: `${envVar} must also be listed in ${envPath}`,
      });
    }
  }
}

function parseAppPorts(
  value: unknown,
  issues: ProviderPackageMetadataIssue[],
): ProviderPackagePortMetadata[] | undefined {
  if (value === undefined) return undefined;
  const path = "beignet.provider.appPorts";
  if (!Array.isArray(value)) {
    issues.push({ path, message: "must be an array of port metadata objects" });
    return undefined;
  }

  const parsed: ProviderPackagePortMetadata[] = [];
  for (const [index, entry] of value.entries()) {
    const entryPath = `${path}[${index}]`;
    if (!isRecord(entry)) {
      issues.push({ path: entryPath, message: "must be an object" });
      continue;
    }
    if (typeof entry.name !== "string") {
      issues.push({ path: `${entryPath}.name`, message: "must be a string" });
    }
    if (typeof entry.type !== "string") {
      issues.push({ path: `${entryPath}.type`, message: "must be a string" });
    }
    if (typeof entry.name === "string" && typeof entry.type === "string") {
      parsed.push({ name: entry.name, type: entry.type });
    }
  }

  return parsed;
}

function parseVariants(
  value: unknown,
  issues: ProviderPackageMetadataIssue[],
): ProviderPackageVariantMetadata[] | undefined {
  if (value === undefined) return undefined;
  const path = "beignet.provider.variants";
  if (!Array.isArray(value)) {
    issues.push({ path, message: "must be an array of variant objects" });
    return undefined;
  }
  if (value.length === 0) {
    issues.push({
      path,
      message: "must include at least one variant when present",
    });
    return undefined;
  }

  const parsed: ProviderPackageVariantMetadata[] = [];
  const seenNames = new Set<string>();
  for (const [index, entry] of value.entries()) {
    const entryPath = `${path}[${index}]`;
    if (!isRecord(entry)) {
      issues.push({ path: entryPath, message: "must be an object" });
      continue;
    }

    let name: string | undefined;
    if (typeof entry.name !== "string" || entry.name.length === 0) {
      issues.push({
        path: `${entryPath}.name`,
        message: "must be a non-empty string",
      });
    } else if (seenNames.has(entry.name)) {
      issues.push({
        path: `${entryPath}.name`,
        message: `duplicates variant name "${entry.name}"`,
      });
    } else {
      seenNames.add(entry.name);
      name = entry.name;
    }

    let displayName: string | undefined;
    if (entry.displayName !== undefined) {
      if (typeof entry.displayName !== "string") {
        issues.push({
          path: `${entryPath}.displayName`,
          message: "must be a string",
        });
      } else {
        displayName = entry.displayName;
      }
    }

    const env = parseStringArray(entry.env, `${entryPath}.env`, issues);
    const requiredEnv = parseStringArray(
      entry.requiredEnv,
      `${entryPath}.requiredEnv`,
      issues,
    );
    if (requiredEnv !== undefined) {
      validateRequiredEnvMembers(
        requiredEnv,
        env,
        `${entryPath}.requiredEnv`,
        `${entryPath}.env`,
        issues,
      );
    }

    const requiredEnvAlternatives = parseStringArrayArray(
      entry.requiredEnvAlternatives,
      `${entryPath}.requiredEnvAlternatives`,
      issues,
    );
    if (requiredEnvAlternatives !== undefined) {
      for (const alternative of requiredEnvAlternatives) {
        validateRequiredEnvMembers(
          alternative,
          env,
          `${entryPath}.requiredEnvAlternatives`,
          `${entryPath}.env`,
          issues,
        );
      }
    }
    if (
      entry.requiredEnv !== undefined &&
      entry.requiredEnvAlternatives !== undefined
    ) {
      issues.push({
        path: `${entryPath}.requiredEnvAlternatives`,
        message: `must not be set alongside ${entryPath}.requiredEnv`,
      });
    }

    const requiredTables = parseStringArray(
      entry.requiredTables,
      `${entryPath}.requiredTables`,
      issues,
    );

    const registration = parseRegistration(
      entry.registration,
      issues,
      `${entryPath}.registration`,
    );

    if (name === undefined) continue;
    const variant: ProviderPackageVariantMetadata = { name };
    if (displayName !== undefined) variant.displayName = displayName;
    if (env !== undefined) variant.env = env;
    if (requiredEnv !== undefined) variant.requiredEnv = requiredEnv;
    if (requiredEnvAlternatives !== undefined) {
      variant.requiredEnvAlternatives = requiredEnvAlternatives;
    }
    if (requiredTables !== undefined) variant.requiredTables = requiredTables;
    if (registration !== undefined) variant.registration = registration;
    parsed.push(variant);
  }

  return parsed;
}

function parseRegistration(
  value: unknown,
  issues: ProviderPackageMetadataIssue[],
  path: string,
): ProviderPackageRegistrationMetadata | undefined {
  if (value === undefined) return undefined;
  if (!isRecord(value)) {
    issues.push({ path, message: "must be an object" });
    return undefined;
  }

  const registration: ProviderPackageRegistrationMetadata = {};
  if (value.required !== undefined) {
    if (typeof value.required !== "boolean") {
      issues.push({ path: `${path}.required`, message: "must be a boolean" });
    } else {
      registration.required = value.required;
    }
  }

  if (value.severity !== undefined) {
    if (value.severity !== "warning" && value.severity !== "hint") {
      issues.push({
        path: `${path}.severity`,
        message: 'must be "warning" or "hint"',
      });
    } else {
      registration.severity = value.severity;
    }
  }

  if (registration.required === true && registration.severity === "hint") {
    issues.push({
      path: `${path}.severity`,
      message: 'must not be "hint" when registration is required',
    });
  }

  const tokens = parseStringArray(value.tokens, `${path}.tokens`, issues);
  if (tokens !== undefined) registration.tokens = tokens;

  if ((tokens?.length ?? 0) === 0) {
    if (registration.required === true) {
      issues.push({
        path: `${path}.tokens`,
        message:
          "must include at least one token when registration is required",
      });
    } else if (registration.severity !== undefined) {
      issues.push({
        path: `${path}.tokens`,
        message:
          "must include at least one token when registration severity is set",
      });
    }
  }

  return registration;
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}
