/**
 * Product Manifest Specification
 *
 * Defines the JSON schema for product.json files that capture everything
 * about a ByteLyst product — identity, theme, features, containers, flags, ports.
 *
 * @module @bytelyst/config/product-manifest
 */

import { readFileSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { z } from 'zod';

/**
 * Platform identifiers
 */
export const PlatformSchema = z.enum(['web', 'ios', 'android', 'desktop', 'watch', 'mac']);

/**
 * Theme color token
 */
export const ColorTokenSchema = z.string().regex(/^#[0-9A-Fa-f]{6}$/, {
  message: 'Color must be a hex value like #5AE68C',
});

/**
 * Theme specification
 */
export const ThemeSchema = z.object({
  primary: ColorTokenSchema,
  secondary: ColorTokenSchema,
  accent: ColorTokenSchema,
  background: ColorTokenSchema,
  surface: ColorTokenSchema,
  text: ColorTokenSchema,
  error: ColorTokenSchema,
  warning: ColorTokenSchema,
  success: ColorTokenSchema,
});

/**
 * Cosmos container definition
 */
export const ContainerDefSchema = z.object({
  name: z.string().min(1),
  partitionKey: z.string().min(1),
  ttlSeconds: z.number().positive().optional(),
  uniqueKeys: z.array(z.string()).optional(),
});

/**
 * Feature flag default
 */
export const FeatureFlagSchema = z.object({
  key: z.string().min(1),
  defaultValue: z.union([z.boolean(), z.string(), z.number()]),
  description: z.string().optional(),
});

/**
 * Port configuration
 */
export const PortConfigSchema = z.object({
  service: z.number().optional(),
  dashboard: z.number().optional(),
  web: z.number().optional(),
});

/**
 * Bundle ID — either a single reverse-DNS string or per-platform object
 */
export const BundleIdSchema = z.union([
  z.string().min(1),
  z.object({
    ios: z.string().optional(),
    android: z.string().optional(),
    web: z.string().optional(),
  }),
]);

/**
 * App store metadata (optional)
 */
export const AppStoreSchema = z
  .object({
    category: z.string().optional(),
    subcategory: z.string().optional(),
    ageRating: z.string().optional(),
    privacyUrl: z.string().url().optional(),
    termsUrl: z.string().url().optional(),
    supportUrl: z.string().url().optional(),
  })
  .optional();

/**
 * Product manifest schema (Zod)
 *
 * Designed to accommodate the real-world variety of product.json files
 * across the ByteLyst ecosystem. All products use `productId` as the
 * primary identifier.
 *
 * Example product.json:
 * ```json
 * {
 *   "productId": "lysnrai",
 *   "displayName": "LysnrAI",
 *   "bundleId": "com.saravana.lysnrai",
 *   "domain": "lysnrai.app",
 *   "description": "Voice-to-text dictation platform",
 *   "backendPort": 4015,
 *   "platforms": ["web", "ios", "android", "desktop"],
 *   "cosmos": {
 *     "containers": [
 *       { "name": "transcripts", "partitionKey": "/userId" },
 *       { "name": "sessions", "partitionKey": "/userId" }
 *     ]
 *   },
 *   "ports": {
 *     "service": 4015,
 *     "dashboard": 3002
 *   }
 * }
 * ```
 */
const BaseManifestSchema = z.object({
  // Identity (productId required, rest optional for minimal manifests)
  productId: z.string().regex(/^[a-z][a-z0-9-]*$/, {
    message: 'Product ID must be lowercase alphanumeric/hyphens, starting with letter',
  }),
  displayName: z.string().min(1).max(50),

  // Optional identity fields
  name: z.string().min(1).max(50).optional(),
  bundleId: BundleIdSchema.optional(),
  domain: z.string().optional(),
  tagline: z.string().max(200).optional(),
  description: z.string().max(500).optional(),
  version: z
    .string()
    .regex(/^\d+\.\d+\.\d+$/)
    .optional(),

  // Platforms (defaults to web)
  platforms: z.array(PlatformSchema).default(['web']),
  primarySurface: PlatformSchema.optional(),
  mobileCompanion: z.boolean().optional(),

  // Backend port (convenience — also available in ports.service)
  backendPort: z.number().min(1024).max(65535).optional(),

  // Legacy identity fields from older product.json files
  licensePrefix: z.string().optional(),
  configDirName: z.string().optional(),
  envVarPrefix: z.string().optional(),
  bundleIdSuffix: z.string().optional(),
  packageName: z.string().optional(),
  appGroup: z.string().optional(),

  // Per-platform bundle IDs (alternative to bundleId)
  bundleIds: z.record(z.string(), z.string()).optional(),

  // App store metadata
  appStore: AppStoreSchema,

  // Theming (optional, uses defaults if not specified)
  theme: ThemeSchema.partial().optional(),

  // Feature map (key → boolean/string/number)
  features: z.record(z.string(), z.boolean().or(z.string()).or(z.number())).optional(),

  // Cosmos containers
  cosmos: z
    .object({
      containers: z.array(ContainerDefSchema).default([]),
    })
    .optional(),

  // Feature flags with defaults
  flags: z.array(FeatureFlagSchema).default([]),

  // Port configuration
  ports: PortConfigSchema.optional(),

  // Agent/AI fields (used by FlowMonk, ActionTrail)
  backendAuthority: z.string().optional(),
  planningEngine: z.string().optional(),
  aiRole: z.array(z.string()).optional(),
});

export const ProductManifestSchema = BaseManifestSchema.superRefine((data, ctx) => {
  // Validate no duplicate container names
  const containers = data.cosmos?.containers;
  if (containers && containers.length > 1) {
    const names = containers.map(c => c.name);
    const seen = new Set<string>();
    for (let i = 0; i < names.length; i++) {
      if (seen.has(names[i])) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          message: `Duplicate container name: ${names[i]}`,
          path: ['cosmos', 'containers', i, 'name'],
        });
      }
      seen.add(names[i]);
    }
  }
});

/**
 * Extended manifest that allows additional unknown keys.
 * Use this when you need to access custom fields not in the schema.
 */
export const ExtendedProductManifestSchema = BaseManifestSchema.passthrough();

/**
 * Inferred TypeScript type for ProductManifest
 */
export type ProductManifest = z.infer<typeof ProductManifestSchema>;
export type Platform = z.infer<typeof PlatformSchema>;
export type Theme = z.infer<typeof ThemeSchema>;
export type ContainerDef = z.infer<typeof ContainerDefSchema>;
export type FeatureFlag = z.infer<typeof FeatureFlagSchema>;
export type PortConfig = z.infer<typeof PortConfigSchema>;

/**
 * Default theme colors (ByteLyst brand palette)
 */
export const DEFAULT_THEME: Theme = {
  primary: '#5AE68C',
  secondary: '#5A8CFF',
  accent: '#2EE6D6',
  background: '#06070A',
  surface: '#121725',
  text: '#EFF4FF',
  error: '#FF6E6E',
  warning: '#F59E0B',
  success: '#34D399',
};

/**
 * Load and validate a product manifest from a file path
 *
 * @param path Path to product.json file
 * @returns Validated ProductManifest
 * @throws ZodError if validation fails
 *
 * @example
 * ```ts
 * const manifest = await loadProductManifest('./product.json');
 * console.log(manifest.productId); // 'lysnrai'
 * ```
 */
export async function loadProductManifest(path: string): Promise<ProductManifest> {
  const content = await readFile(path, 'utf-8');
  const json = JSON.parse(content);
  return ProductManifestSchema.parse(json);
}

/**
 * Synchronous version of loadProductManifest (for startup use)
 *
 * @param path Path to product.json file
 * @returns Validated ProductManifest
 * @throws ZodError if validation fails
 */
export function loadProductManifestSync(path: string): ProductManifest {
  const content = readFileSync(path, 'utf-8');
  const json = JSON.parse(content);
  return ProductManifestSchema.parse(json);
}

/**
 * Merge manifest theme with defaults
 *
 * @param manifest Product manifest
 * @returns Complete theme with defaults filled in
 */
export function resolveTheme(manifest: ProductManifest): Theme {
  return {
    ...DEFAULT_THEME,
    ...manifest.theme,
  };
}

/**
 * Validate a product manifest object without loading from file
 *
 * @param json Parsed JSON object
 * @returns Validated ProductManifest
 * @throws ZodError if validation fails
 */
export function validateProductManifest(json: unknown): ProductManifest {
  return ProductManifestSchema.parse(json);
}

/**
 * Safe validation that returns null on failure
 *
 * @param json Parsed JSON object
 * @returns Validated ProductManifest or null
 */
export function safeValidateProductManifest(json: unknown): ProductManifest | null {
  const result = ProductManifestSchema.safeParse(json);
  return result.success ? result.data : null;
}
