export interface IManifestDiagnostic {
  readonly severity: 'error' | 'warn';
  readonly code: string;
  readonly message: string;
  readonly field?: string;
  readonly componentIndex?: number;
}

export interface IManifestValidationResult {
  readonly valid: boolean;
  readonly errors: string[];
  readonly diagnostics: ReadonlyArray<IManifestDiagnostic>;
}

type JsonRecord = Record<string, unknown>;

const SUPPORTED_FS_POLICIES = new Set([
  'none',
  'matrix-only',
  'project-readonly',
  'project-readwrite',
  'global-readonly',
]);
const SUPPORTED_HOST_API_POLICIES = new Set([
  'none',
  'same-origin',
]);

function asRecord(value: unknown): JsonRecord | null {
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    return null;
  }
  return value as JsonRecord;
}

function optionalTrimmedString(value: unknown): string | undefined {
  if (typeof value !== 'string') {
    return undefined;
  }
  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed : undefined;
}

function validateInstallHook(
  diagnostics: IManifestDiagnostic[],
  install: JsonRecord,
  phase: 'validate' | 'migrate' | 'seed' | 'verify',
): void {
  const hook = asRecord(install[phase]);
  if (!hook) {
    if (install[phase] !== undefined) {
      pushError(diagnostics, {
        code: 'MXPKG_INSTALL_HOOK_INVALID',
        field: `install.${phase}`,
        message: `install.${phase} must be an object when provided`,
      });
    }
    return;
  }

  if (!optionalTrimmedString(hook.script)) {
    pushError(diagnostics, {
      code: 'MXPKG_INSTALL_HOOK_SCRIPT_INVALID',
      field: `install.${phase}.script`,
      message: `install.${phase}.script must be a non-empty string`,
    });
  }
}

function pushError(
  diagnostics: IManifestDiagnostic[],
  entry: Omit<IManifestDiagnostic, 'severity'>
): void {
  diagnostics.push({ ...entry, severity: 'error' });
}

export function validateManifestForMxCli(manifest: unknown): IManifestValidationResult {
  const diagnostics: IManifestDiagnostic[] = [];
  const manifestRecord = asRecord(manifest);
  if (!manifestRecord) {
    pushError(diagnostics, {
      code: 'MXPKG_MANIFEST_NOT_OBJECT',
      message: 'Manifest must be a JSON object',
    });
    return {
      valid: false,
      diagnostics,
      errors: diagnostics.map((entry) => entry.message),
    };
  }

  if (!optionalTrimmedString(manifestRecord.name)) {
    pushError(diagnostics, {
      code: 'MXPKG_NAME_INVALID',
      field: 'name',
      message: 'name must be a non-empty string',
    });
  }

  if (!optionalTrimmedString(manifestRecord.version)) {
    pushError(diagnostics, {
      code: 'MXPKG_VERSION_INVALID',
      field: 'version',
      message: 'version must be a non-empty string',
    });
  }

  const runtime = asRecord(manifestRecord.runtime);
  if (!runtime || !optionalTrimmedString(runtime.language) || !optionalTrimmedString(runtime.entry)) {
    pushError(diagnostics, {
      code: 'MXPKG_RUNTIME_INVALID',
      field: 'runtime',
      message: 'runtime.language and runtime.entry are required',
    });
  }

  const permissions = asRecord(manifestRecord.permissions);
  const fsPolicy = permissions ? optionalTrimmedString(permissions.fsPolicy) : undefined;
  if (!fsPolicy || !SUPPORTED_FS_POLICIES.has(fsPolicy)) {
    pushError(diagnostics, {
      code: 'MXPKG_PERMISSIONS_FSPOLICY_INVALID',
      field: 'permissions.fsPolicy',
      message: 'permissions.fsPolicy must be one of none|matrix-only|project-readonly|project-readwrite|global-readonly',
    });
  }
  const hostApi = permissions ? optionalTrimmedString(permissions.hostApi) : undefined;
  if (permissions?.hostApi !== undefined && (!hostApi || !SUPPORTED_HOST_API_POLICIES.has(hostApi))) {
    pushError(diagnostics, {
      code: 'MXPKG_PERMISSIONS_HOSTAPI_INVALID',
      field: 'permissions.hostApi',
      message: 'permissions.hostApi must be one of none|same-origin',
    });
  }
  for (const field of ['network', 'subprocess', 'env']) {
    if (permissions && permissions[field] !== undefined && typeof permissions[field] !== 'boolean') {
      pushError(diagnostics, {
        code: 'MXPKG_PERMISSIONS_BOOLEAN_INVALID',
        field: `permissions.${field}`,
        message: `permissions.${field} must be boolean when provided`,
      });
    }
  }

  const install = asRecord(manifestRecord.install);
  if (manifestRecord.install !== undefined && !install) {
    pushError(diagnostics, {
      code: 'MXPKG_INSTALL_INVALID',
      field: 'install',
      message: 'install must be an object when provided',
    });
  } else if (install) {
    validateInstallHook(diagnostics, install, 'validate');
    validateInstallHook(diagnostics, install, 'migrate');
    validateInstallHook(diagnostics, install, 'seed');
    validateInstallHook(diagnostics, install, 'verify');
  }

  const componentsValue = manifestRecord.components;
  if (!Array.isArray(componentsValue)) {
    pushError(diagnostics, {
      code: 'MXPKG_COMPONENTS_NOT_ARRAY',
      field: 'components',
      message: 'components must be an array',
    });
  } else {
    componentsValue.forEach((entry, componentIndex) => {
      const component = asRecord(entry);
      if (!component) {
        pushError(diagnostics, {
          code: 'MXPKG_COMPONENT_NOT_OBJECT',
          field: 'components',
          componentIndex,
          message: `component[${componentIndex}] must be an object`,
        });
        return;
      }

      const type = optionalTrimmedString(component.type);
      if (!type) {
        pushError(diagnostics, {
          code: 'MXPKG_COMPONENT_TYPE_INVALID',
          field: 'type',
          componentIndex,
          message: `component[${componentIndex}].type must be a non-empty string`,
        });
        return;
      }

      const exportName = optionalTrimmedString(component.export);
      if (component.export !== undefined && !exportName) {
        pushError(diagnostics, {
          code: 'MXPKG_COMPONENT_EXPORT_INVALID',
          field: 'export',
          componentIndex,
          message: `component[${componentIndex}].export must be a non-empty string when provided`,
        });
        return;
      }

      const mount = optionalTrimmedString(component.mount);
      if (component.mount !== undefined && !mount) {
        pushError(diagnostics, {
          code: 'MXPKG_COMPONENT_MOUNT_INVALID',
          field: 'mount',
          componentIndex,
          message: `component[${componentIndex}].mount must be a non-empty string when provided`,
        });
        return;
      }

      if (component.autoStart !== undefined && typeof component.autoStart !== 'boolean') {
        pushError(diagnostics, {
          code: 'MXPKG_COMPONENT_AUTOSTART_INVALID',
          field: 'autoStart',
          componentIndex,
          message: `component[${componentIndex}].autoStart must be boolean when provided`,
        });
        return;
      }

      if (component.props !== undefined && !asRecord(component.props)) {
        pushError(diagnostics, {
          code: 'MXPKG_COMPONENT_PROPS_INVALID',
          field: 'props',
          componentIndex,
          message: `component[${componentIndex}].props must be an object when provided`,
        });
        return;
      }

      const componentFsPolicy = optionalTrimmedString(component.fsPolicy);
      if (
        component.fsPolicy !== undefined
        && (!componentFsPolicy || !SUPPORTED_FS_POLICIES.has(componentFsPolicy))
      ) {
        pushError(diagnostics, {
          code: 'MXPKG_COMPONENT_FSPOLICY_INVALID',
          field: 'fsPolicy',
          componentIndex,
          message: `component[${componentIndex}].fsPolicy must be one of matrix-only|project-readonly|project-readwrite|global-readonly|none`,
        });
      }
    });
  }

  const errors = diagnostics
    .filter((entry) => entry.severity === 'error')
    .map((entry) => entry.message);

  return {
    valid: errors.length === 0,
    diagnostics,
    errors,
  };
}
