import { z } from 'zod';
import { Json, LooseRecord, Yaml } from '../../../util/schema-utils';

export const PnpmCatalogs = z.object({
  catalog: z.optional(z.record(z.string())),
  catalogs: z.optional(z.record(z.record(z.string()))),
});

export const YarnConfig = Yaml.pipe(
  z.object({
    npmRegistryServer: z.string().optional(),
    npmScopes: z
      .record(
        z.object({
          npmRegistryServer: z.string().optional(),
        }),
      )
      .optional(),
  }),
);

export type YarnConfig = z.infer<typeof YarnConfig>;

export const PnpmWorkspaceFile = z
  .object({
    packages: z.array(z.string()),
  })
  .and(PnpmCatalogs);
export type PnpmWorkspaceFile = z.infer<typeof PnpmWorkspaceFile>;

export const PackageManager = z
  .string()
  .transform((val) => val.split('@'))
  .transform(([name, ...version]) => ({ name, version: version.join('@') }));

const DevEngineDependency = z.object({
  name: z.string(),
  version: z.string().optional(),
});

const DevEngine = z.object({
  packageManager: DevEngineDependency.or(
    z.array(DevEngineDependency),
  ).optional(),
});

export const PackageJson = Json.pipe(
  z.object({
    devEngines: DevEngine.optional(),
    engines: LooseRecord(z.string()).optional(),
    dependencies: LooseRecord(z.string()).optional(),
    devDependencies: LooseRecord(z.string()).optional(),
    peerDependencies: LooseRecord(z.string()).optional(),
    packageManager: PackageManager.optional(),
    volta: LooseRecord(z.string()).optional(),
  }),
);

export type PackageJson = z.infer<typeof PackageJson>;

export const PackageLockV3 = z.object({
  lockfileVersion: z.literal(3),
  packages: LooseRecord(
    z
      .string()
      .transform((x) => x.replace(/^node_modules\//, ''))
      .refine((x) => x.trim() !== ''),
    z.object({ version: z.string() }),
  ),
});

export const PackageLockPreV3 = z
  .object({
    lockfileVersion: z.union([z.literal(2), z.literal(1)]),
    dependencies: LooseRecord(z.object({ version: z.string() })),
  })
  .transform(({ lockfileVersion, dependencies: packages }) => ({
    lockfileVersion,
    packages,
  }));

export const PackageLock = Json.pipe(
  z.union([PackageLockV3, PackageLockPreV3]),
).transform(({ packages, lockfileVersion }) => {
  const lockedVersions: Record<string, string> = {};
  for (const [entry, val] of Object.entries(packages)) {
    lockedVersions[entry] = val.version;
  }
  return { lockedVersions, lockfileVersion };
});
