export interface VisibilityWhere {
  clause: string;
  params: unknown[];
}

export interface VisibilityOptions {
  /**
   * 行がスペースに属する場合の space_id 列名（例 `'space_id'`、spaces 自身は `'id'`）。
   * 指定かつ非 admin のとき、可視性節に「スペースのメンバーなら閲覧可」の OR ブランチを足す。
   * **コード制御のリテラルのみ**（tableAlias と同じ信頼レベル）。ユーザー入力を渡してはならない。
   */
  spaceColumn?: string;
}

export function buildVisibilityWhere(
  user: Express.User,
  tableAlias: string,
  opts?: VisibilityOptions,
): VisibilityWhere {
  if (user.role === 'admin') {
    return { clause: '1=1', params: [] };
  }
  // Synthetic users (no-auth 'local' user, internal callers) may omit orgIds —
  // treat as no org membership rather than throwing on `.length`.
  const orgIds = user.orgIds ?? [];
  const orgPlaceholders = orgIds.length > 0
    ? orgIds.map(() => '?').join(',')
    : 'NULL';
  // 既存の params 順は [user.id, ...user.orgIds]。membership の user.id は必ず末尾に足す（順序が重要）。
  const params: unknown[] = [user.id, ...orgIds];
  let membershipBranch = '';
  if (opts?.spaceColumn) {
    // スペース行の閲覧資格 = 明示メンバー（space_members）OR スペースの根オーナー
    // （spaces.owner_id）。根オーナーは通常 space_members 行を持たないため、
    // owner サブクエリを足さないと自分のスペースの「他メンバーの行」が見えない（gap-1）。
    // user.id を clause 出現順（members → spaces）に 2 回 push する。
    membershipBranch =
      ` OR (${tableAlias}.${opts.spaceColumn} IN (SELECT space_id FROM space_members WHERE user_id = ?)` +
      ` OR ${tableAlias}.${opts.spaceColumn} IN (SELECT id FROM spaces WHERE owner_id = ?))`;
    params.push(user.id, user.id);
  }
  return {
    clause: `(
      ${tableAlias}.owner_id = ?
      OR ${tableAlias}.visibility = 'public'
      OR (${tableAlias}.visibility = 'org' AND ${tableAlias}.visibility_scope_org_id IN (${orgPlaceholders}))${membershipBranch}
    )`.replace(/\s+/g, ' ').trim(),
    params,
  };
}

/**
 * `spaces` テーブル専用の可視性 WHERE。通常の行可視性と決定的に違うのは
 * **個人スペース（kind='personal'）は所有者本人にしか見えない（admin も例外でない）**
 * という点。これがないと admin の listSpaces が他ユーザーの個人スペースを拾い、
 * UI が「最初に見つかった personal」を自分のワークスペースと誤認する（誤った
 * members-owner / AGENTS.md / MCP / 設定で動く root cause）。
 *
 * - `kind='personal'` 行: `s.owner_id = ?`（viewer.id）のみ。role を問わない。
 * - `kind='case'`     行: 従来の `buildVisibilityWhere`（admin=1=1 / owner / org /
 *   public / space メンバーシップ OR ブランチ）をそのまま適用。
 *
 * param 順は SQL の出現順に合わせる: 先頭に personal-owner の viewer.id、続いて
 * `buildVisibilityWhere` が返す params（admin のときは空）。`tableAlias` は呼び出し
 * 側のコード制御リテラルのみ（buildVisibilityWhere と同じ信頼レベル）。
 */
export function buildSpaceVisibilityWhere(
  user: Express.User,
  tableAlias: string,
): VisibilityWhere {
  const base = buildVisibilityWhere(user, tableAlias, { spaceColumn: 'id' });
  const clause =
    `(
      (${tableAlias}.kind = 'personal' AND ${tableAlias}.owner_id = ?)
      OR (${tableAlias}.kind != 'personal' AND ${base.clause})
    )`.replace(/\s+/g, ' ').trim();
  // 出現順: personal-owner の viewer.id → base の params。
  return { clause, params: [user.id, ...base.params] };
}

export function canEditEntity(user: Express.User, entity: { ownerId: string | null }): boolean {
  return user.role === 'admin' || entity.ownerId === user.id;
}

export type SpaceMemberRole = 'owner' | 'editor' | 'viewer';

/**
 * スペースの管理操作（メンバー管理・スペース設定変更・archive）を許可するか。
 * admin / 根オーナー（space.ownerId）/ member.role==='owner' なら true。
 * `memberRole` は Repository.getSpaceMemberRole で事前取得したロール（DB アクセスは Repository に置く）。
 */
export function canManageSpace(
  user: Express.User,
  space: { ownerId: string | null },
  memberRole?: SpaceMemberRole | null,
): boolean {
  return (
    user.role === 'admin' ||
    (space.ownerId !== null && space.ownerId === user.id) ||
    memberRole === 'owner'
  );
}

/**
 * スペース配下の行編集（タスク継続/削除・カレンダー CRUD・ファイル upload 等）を
 * スペースのメンバーシップ起点で許可するか。canManageSpace に加え editor も許可。
 * viewer は read のみ（buildVisibilityWhere が扱う）で書き込み不可。
 */
export function canEditInSpace(
  user: Express.User,
  space: { ownerId: string | null },
  memberRole?: SpaceMemberRole | null,
): boolean {
  return canManageSpace(user, space, memberRole) || memberRole === 'editor';
}

/**
 * 指定 user が指定タスクを閲覧できるか判定する。SQL ではなくロード済みオブジェクト
 * に対して使う。`buildVisibilityWhere` と同じセマンティクスをコード上で再現する。
 *
 * - admin: 常に true
 * - owner: 常に true
 * - public: 常に true
 * - org: user.orgIds に visibilityScopeOrgId が含まれているかで判定
 * - スペースメンバー/根オーナー: `spaceAccessible === true` のとき true
 *   （呼び出し側が `repo.userCanViewSpace(task.spaceId, user)` で算出して渡す。
 *    この関数自体は DB を触らず純粋に保つ）
 * - private: 上記いずれにも該当しなければ false
 */
export function canUserSeeTask(
  user: Express.User,
  task: { ownerId: string | null; visibility: 'private' | 'org' | 'public'; visibilityScopeOrgId: string | null },
  spaceAccessible?: boolean,
): boolean {
  if (user.role === 'admin') return true;
  if (task.ownerId !== null && task.ownerId === user.id) return true;
  if (task.visibility === 'public') return true;
  if (task.visibility === 'org' && task.visibilityScopeOrgId !== null) {
    if (user.orgIds.includes(task.visibilityScopeOrgId)) return true;
  }
  // スペースメンバーシップ起点の閲覧許可（buildVisibilityWhere の OR ブランチに対応）。
  if (spaceAccessible === true) return true;
  return false;
}
