export interface ViewMatchRule {
  stateShape?: {
    field: string;
    type: 'array' | 'object' | 'map' | 'set' | 'primitive';
    elementShape?: string;
  };
  dataType?: string;
  componentClass?: string;
  hasChildren?: boolean;
  hasDom?: boolean;
  priority: number;
}

export interface ViewTypeRegistration {
  namespace: string;
  name: string;
  tag: string;
  matches: ViewMatchRule[];
}

export interface ActorViewProfile {
  componentClass?: string;
  hasChildren?: boolean;
  hasDom?: boolean;
  state?: Record<string, unknown>;
  stateSchema?: Record<string, unknown>;
}

type ViewStateShapeType = NonNullable<ViewMatchRule['stateShape']>['type'];

export class ViewTypeRegistry {
  private static readonly _entries: ViewTypeRegistration[] = [];

  static register(entry: ViewTypeRegistration): void {
    this._entries.push(entry);
  }

  static list(): ViewTypeRegistration[] {
    return [...this._entries];
  }

  static match(profile: ActorViewProfile): ViewTypeRegistration[] {
    return this._entries
      .map((entry) => ({
        entry,
        score: entry.matches.reduce((acc, rule) => acc + this._scoreRule(rule, profile), 0),
      }))
      .filter((x) => x.score > 0)
      .sort((a, b) => b.score - a.score)
      .map((x) => x.entry);
  }

  private static _scoreRule(rule: ViewMatchRule, profile: ActorViewProfile): number {
    let score = 0;

    if (rule.componentClass && rule.componentClass === profile.componentClass) {
      score += rule.priority;
    }

    if (typeof rule.hasChildren === 'boolean' && rule.hasChildren === !!profile.hasChildren) {
      score += rule.priority;
    }

    if (typeof rule.hasDom === 'boolean' && rule.hasDom === !!profile.hasDom) {
      score += rule.priority;
    }

    if (rule.dataType && profile.stateSchema) {
      const hasType = Object.values(profile.stateSchema).some((v) => {
        if (typeof v !== 'object' || v === null) return false;
        return (v as { type?: string }).type === rule.dataType;
      });
      if (hasType) {
        score += rule.priority;
      }
    }

    if (rule.stateShape && profile.state) {
      const value = profile.state[rule.stateShape.field];
      if (this._matchesStateType(value, rule.stateShape.type)) {
        score += rule.priority;
      }
    }

    return score;
  }

  private static _matchesStateType(value: unknown, type: ViewStateShapeType): boolean {
    if (type === 'array') return Array.isArray(value);
    if (type === 'object') return typeof value === 'object' && value !== null && !Array.isArray(value);
    if (type === 'map') return value instanceof Map;
    if (type === 'set') return value instanceof Set;
    if (type === 'primitive') return value === null || (typeof value !== 'object' && typeof value !== 'function');
    return false;
  }
}
