"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateAuthorizationPolicyService = generateAuthorizationPolicyService;
const generator_1 = require("@postxl/generator");
function stringifyAuthRuleSet(input) {
    if (!input) {
        return '{}';
    }
    const typedInput = input;
    const result = {};
    if (typedInput.read) {
        result.read = typedInput.read;
    }
    if (typedInput.write) {
        result.write = typedInput.write;
    }
    if (typedInput.create) {
        result.create = typedInput.create;
    }
    if (typedInput.update) {
        result.update = typedInput.update;
    }
    if (typedInput.delete) {
        result.delete = typedInput.delete;
    }
    if (typedInput.actions) {
        result.actions = typedInput.actions;
    }
    if (typedInput.adminUi) {
        result.adminUi = typedInput.adminUi;
    }
    return JSON.stringify(result);
}
function stringifyAuthScopeRuleSet(input) {
    if (!input) {
        return '{}';
    }
    const typedInput = input;
    const result = {};
    if (typedInput.actions) {
        result.actions = typedInput.actions;
    }
    return JSON.stringify(result);
}
function generateAuthorizationPolicyService({ actions, schema, types }) {
    const imports = generator_1.ImportGenerator.from(actions.authorization.policy.location)
        .addType(types.modelNames)
        .addType(types.authorizationContext)
        .addType(types.userRoles)
        .addType(actions.summary.action);
    const schemaRules = [...schema.schemaAuth.entries()]
        .map(([schemaName, ruleSet]) => `    ${JSON.stringify(schemaName)}: ${stringifyAuthRuleSet(ruleSet)},`)
        .join('\n');
    const modelRules = schema.modelNames
        .map((modelName) => {
        const model = schema.models.get(modelName);
        return `    ${JSON.stringify(modelName)}: ${stringifyAuthRuleSet(model?.auth)},`;
    })
        .join('\n');
    const modelSchemas = schema.modelNames
        .map((modelName) => {
        const model = schema.models.get(modelName);
        return `    ${JSON.stringify(modelName)}: ${JSON.stringify(model?.databaseSchema ?? schema.defaultDatabaseSchema)},`;
    })
        .join('\n');
    const scopeRules = Object.entries(schema.auth?.scopes ?? {})
        .map(([scope, ruleSet]) => `    ${JSON.stringify(scope)}: ${stringifyAuthScopeRuleSet(ruleSet)},`)
        .join('\n');
    const knownScopes = new Set();
    for (const provider of actions.providers) {
        for (const scope of provider.scopes) {
            knownScopes.add(scope);
        }
    }
    const scopeToModel = schema.modelNames
        .map((modelName) => {
        const scope = `${modelName.charAt(0).toLowerCase()}${modelName.slice(1)}`;
        if (!knownScopes.has(scope)) {
            return null;
        }
        return `    ${JSON.stringify(scope)}: ${JSON.stringify(modelName)},`;
    })
        .filter(Boolean)
        .join('\n');
    return /* ts */ `
import { Injectable } from '@nestjs/common'

${imports.generate()}

type RoleRule = { anyRole: string[] }

type RuleSet = {
  read?: RoleRule
  write?: RoleRule
  create?: RoleRule
  update?: RoleRule
  delete?: RoleRule
  actions?: Record<string, RoleRule>
  adminUi?: {
    visibleFor: string[]
  }
}

type Operation = 'read' | 'write' | 'create' | 'update' | 'delete'
type ActionScope = Action['scope']
type ScopeRuleSet = {
  actions?: Record<string, RoleRule>
}
type PolicyRowId = string | number
type PolicyCheckKind = Operation | 'adminUi' | 'action'
type PolicyContext = {
  authorization: AuthorizationContext
  check: PolicyCheckKind
  modelName?: ModelName
  operation?: Operation
  scope?: ActionScope
  actionType?: string
  id?: PolicyRowId
  item?: unknown
}
type ModelPolicyParams = {
  authorization: AuthorizationContext
  modelName: ModelName
  id?: PolicyRowId
  item?: unknown
}

type PolicyMatrix = {
  defaultDeny: boolean
  schemas: Record<string, RuleSet>
  models: Record<ModelName, RuleSet>
  modelSchemas: Record<ModelName, string>
  scopes: Partial<Record<ActionScope, ScopeRuleSet>>
  scopeModels: Partial<Record<ActionScope, ModelName>>
}

@Injectable()
export class ${actions.authorization.policy.name} {
  private readonly matrix: PolicyMatrix = {
    defaultDeny: ${schema.auth?.defaultDeny ?? true},
    schemas: {
${schemaRules}
    },
    models: {
${modelRules}
    },
    modelSchemas: {
${modelSchemas}
    },
    scopes: {
${scopeRules}
    },
    scopeModels: {
${scopeToModel}
    },
  }

  public canRead(params: ModelPolicyParams): boolean {
    return this.canOperate({ ...params, operation: 'read' })
  }

  public canRunAction(params: {
    authorization: AuthorizationContext
    scope: ActionScope
    actionType: string
    id?: PolicyRowId
    item?: unknown
  }): boolean {
    const scopeRules = this.matrix.scopes[params.scope]
    const scopedRule = scopeRules?.actions?.[params.actionType] ?? scopeRules?.actions?.['*']
    if (scopedRule) {
      const isRoleAllowed = this.matchesAnyRole(params.authorization.userRoles, scopedRule.anyRole)
      return this.applyCustomPolicy(
        {
          authorization: params.authorization,
          check: 'action',
          scope: params.scope,
          actionType: params.actionType,
          id: params.id,
          item: params.item,
        },
        isRoleAllowed,
      )
    }

    const modelName = this.matrix.scopeModels[params.scope]
    if (modelName) {
      const rules = this.getEffectiveRules(modelName)
      const direct = rules.actions?.[params.actionType]
      const wildcard = rules.actions?.['*']
      const selectedRule = direct ?? wildcard

      if (selectedRule) {
        const isRoleAllowed = this.matchesAnyRole(params.authorization.userRoles, selectedRule.anyRole)
        return this.applyCustomPolicy(
          {
            authorization: params.authorization,
            check: 'action',
            modelName,
            scope: params.scope,
            actionType: params.actionType,
            id: params.id,
            item: params.item,
          },
          isRoleAllowed,
        )
      }

      return this.canOperate({
        authorization: params.authorization,
        modelName,
        operation: 'write',
        id: params.id,
        item: params.item,
      })
    }

    return this.applyCustomPolicy(
      {
        authorization: params.authorization,
        check: 'action',
        scope: params.scope,
        actionType: params.actionType,
        id: params.id,
        item: params.item,
      },
      !this.matrix.defaultDeny,
    )
  }

  public canWriteOperation(
    params: ModelPolicyParams & { operation: Exclude<Operation, 'read'> },
  ): boolean {
    return this.canOperate(params)
  }

  public canAccessAdminUi(params: ModelPolicyParams): boolean {
    const rules = this.getEffectiveRules(params.modelName)
    const adminRule = rules.adminUi
    if (adminRule) {
      const isRoleAllowed = this.matchesAnyRole(params.authorization.userRoles, adminRule.visibleFor)
      return this.applyCustomPolicy(
        {
          authorization: params.authorization,
          check: 'adminUi',
          modelName: params.modelName,
          id: params.id,
          item: params.item,
        },
        isRoleAllowed,
      )
    }
    return this.applyCustomPolicy(
      {
        authorization: params.authorization,
        check: 'adminUi',
        modelName: params.modelName,
        id: params.id,
        item: params.item,
      },
      !this.matrix.defaultDeny,
    )
  }

  private canOperate(params: ModelPolicyParams & { operation: Operation }): boolean {
    const rules = this.getEffectiveRules(params.modelName)
    const selectedRule =
      rules[params.operation] ??
      (params.operation === 'create' || params.operation === 'update' || params.operation === 'delete'
        ? rules.write
        : undefined)

    if (!selectedRule) {
      return this.applyCustomPolicy(
        {
          authorization: params.authorization,
          check: params.operation,
          modelName: params.modelName,
          operation: params.operation,
          id: params.id,
          item: params.item,
        },
        !this.matrix.defaultDeny,
      )
    }

    const isRoleAllowed = this.matchesAnyRole(params.authorization.userRoles, selectedRule.anyRole)
    return this.applyCustomPolicy(
      {
        authorization: params.authorization,
        check: params.operation,
        modelName: params.modelName,
        operation: params.operation,
        id: params.id,
        item: params.item,
      },
      isRoleAllowed,
    )
  }

  private applyCustomPolicy(params: PolicyContext, isRoleAllowed: boolean): boolean {
    if (!isRoleAllowed) {
      return false
    }

    const customDecision = this.evaluateCustomPolicy(params)
    return customDecision ?? true
  }

  protected evaluateCustomPolicy(_params: PolicyContext): boolean | undefined {
    // @custom-start:authorizationPolicyRowRules
    return undefined
    // @custom-end:authorizationPolicyRowRules
  }

  private getEffectiveRules(modelName: ModelName): RuleSet {
    const modelRules = this.matrix.models[modelName] ?? {}
    const schemaName = this.matrix.modelSchemas[modelName]
    const schemaRules = schemaName ? (this.matrix.schemas[schemaName] ?? {}) : {}
    return { ...schemaRules, ...modelRules }
  }

  private matchesAnyRole(userRoles: UserRoles, requiredRoles: string[]): boolean {
    for (const role of userRoles) {
      if (requiredRoles.includes(role)) {
        return true
      }
    }
    return false
  }
}
`;
}
//# sourceMappingURL=authorization-policy-service.generator.js.map