-- name: CreateOrganization :one
-- Identity is org_id, presentation is name, and organization names are
-- deliberately not unique. `key` is the optional declarative address
-- (entity-keys §1 to §4): a caller outside the database -- a seed, a
-- configuration loader, an API client -- supplies it to name this row
-- literally. UI-created organizations leave it NULL, and a personal
-- organization never gets one: a key is never derived from a person's login
-- or display name (§4).
INSERT INTO core.organizations (name, org_type, owner_person_id, key)
VALUES ($1, $2, $3, sqlc.narg(key))
RETURNING *;

-- name: GetOrganizationByKey :one
-- Root-scoped key resolver (entity-keys §5): the query that makes `key` a
-- whole feature rather than a seed-only column. uq_organizations_key
-- guarantees at most one row matches; a NULL key never matches because the
-- parameter is compared with `=`.
SELECT * FROM core.organizations
WHERE key = $1;

-- name: GetOrganizationByID :one
SELECT * FROM core.organizations
WHERE org_id = $1;

-- name: GetOrganizationsByOwner :many
SELECT * FROM core.organizations
WHERE owner_person_id = $1
ORDER BY name;

-- name: ListOrganizations :many
SELECT * FROM core.organizations
ORDER BY name;

-- name: ListOrganizationsPage :many
-- Organizations list operator surface (operator-list-scale UX-4): the
-- paged, searched, org-type-filtered successor to ListOrganizationsWithOwner
-- -- each row still carries its owner's display name so no organization can
-- read as unowned (ux-honest-surfaces UX-16). owner_person_id is NOT NULL
-- (every organization has an owner person, the System tenant included --
-- 00001_init.sql), so the LEFT JOIN is defensive rather than expected to
-- ever drop the owner's name.
--
-- sqlc.narg(q): NULL matches every row; set, a case-insensitive substring
-- match against the organization's name, or its owner's display
-- name or primary email (operator-list-scale: "carries a scoped
-- server-side search" -- "organizations: name and owner name or
-- email").
-- sqlc.narg(org_type): NULL matches every org type; set, an exact facet
-- match (operator-list-scale: "Status filters exist ... organizations: org
-- type").
-- count(*) OVER() carries the true total for the filtered set alongside
-- the page's rows (design D2), so the stated total never reflects only the
-- page.
SELECT o.org_id, o.name, o.org_type, o.owner_person_id, o.status, o.created_at, o.updated_at,
       p.display_name AS owner_display_name,
       count(*) OVER() AS total_count
FROM core.organizations o
LEFT JOIN core.persons p ON p.person_id = o.owner_person_id
WHERE (sqlc.narg(q)::text IS NULL
       OR o.name ILIKE '%' || sqlc.narg(q)::text || '%'
       OR p.display_name ILIKE '%' || sqlc.narg(q)::text || '%'
       OR p.primary_email ILIKE '%' || sqlc.narg(q)::text || '%')
  AND (sqlc.narg(org_type)::text IS NULL OR o.org_type = sqlc.narg(org_type)::text)
ORDER BY o.name
LIMIT sqlc.arg(page_limit)::int OFFSET sqlc.arg(page_offset)::int;

-- name: SearchOrganizationsByName :many
-- Landing lookup's organization branch (entity-keys). Runs only
-- when no person matches the lookup term; a dedicated query rather than
-- ListOrganizationsPage because that query's owner-email term would blur
-- the person/organization split the lookup depends on. Active
-- organizations only, name substring match, bounded by LIMIT so a short
-- term cannot return the whole table.
SELECT o.org_id, o.name, o.org_type,
       p.display_name AS owner_display_name
FROM core.organizations o
LEFT JOIN core.persons p ON p.person_id = o.owner_person_id
WHERE o.status = 'active'
  AND o.name ILIKE '%' || sqlc.arg(name)::text || '%'
ORDER BY o.name
LIMIT sqlc.arg(row_limit)::int;

-- name: ListOrganizationsByType :many
SELECT * FROM core.organizations
WHERE org_type = $1
ORDER BY name;

-- name: CountTeamOrganizations :one
-- Operator overview headline. Personal orgs exist one-per-member by
-- structural convention (design/organization/architecture.md §2), so an
-- organizations count that includes them tracks the people count and reads
-- as an error. Negative-space predicate instead of org_type = 'team':
-- everything active that is neither the auto-created personal type nor a
-- reserved type counts, so team, enterprise, and deployment-defined types
-- need no code change here and the reserved system org never appears.
SELECT COUNT(*) FROM core.organizations o
JOIN core.org_types t ON t.org_type = o.org_type
WHERE o.status = 'active' AND o.org_type <> 'personal' AND NOT t.is_reserved;

-- name: EnsureSystemOrganization :exec
-- Idempotently insert the singleton System organization. The partial unique
-- index uq_organizations_one_system enforces "exactly one System tenant", and
-- the conflict target repeats the index predicate so Postgres can infer it.
-- Read back with GetSystemOrganization.
--
-- `system` is one of the two keys the system writes for itself (entity-keys
-- §4): a fixed constant for a row the console creates by design, not a value
-- derived from anybody's data. It is written here rather than by the Go
-- caller so the only path that creates the System tenant is also the only
-- path that names it.
INSERT INTO core.organizations (name, org_type, owner_person_id, key)
VALUES ($1, $2, $3, 'system')
ON CONFLICT (org_type) WHERE org_type = 'system' DO NOTHING;

-- name: GetSystemOrganization :one
-- The singleton System organization, resolved by its natural key. Paired
-- with EnsureSystemOrganization; uq_organizations_one_system guarantees at
-- most one row matches.
SELECT * FROM core.organizations
WHERE org_type = 'system';
