-- ============================================================================
-- 0003 — Promote workspaces to a top-level table.
-- ============================================================================
-- Block A of the EchoDo commercial plan. We:
--   1. Create the new `workspaces` table.
--   2. Copy every existing `objects` row of type='workspace' INTO `workspaces`,
--      preserving the same UUID so existing FKs (which all point at
--      objects.id today) remain valid mid-migration.
--   3. Mint a `slug` for each workspace from its title (or falling back to a
--      short UUID prefix if the slug would collide / be empty).
--   4. Drop the old objects→objects FK on every anchor table and re-add a new
--      FK pointing at workspaces.id.
--   5. Delete the now-redundant type='workspace' rows from `objects` and add
--      NOT NULL on objects.workspace_id (it was nullable for self-references).
-- ============================================================================

-- 1. New workspaces table
CREATE TABLE "workspaces" (
	"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
	"slug" varchar(60) NOT NULL,
	"name" varchar(200) NOT NULL,
	"owner_user_id" uuid NOT NULL,
	"plan_tier" varchar(20) DEFAULT 'free' NOT NULL,
	"archived_at" timestamp with time zone,
	"created_at" timestamp with time zone DEFAULT now() NOT NULL,
	"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint

-- 2. Copy existing workspaces (objects WHERE type='workspace') into the new
--    table. Owner is best-effort: prefer created_by, otherwise the first
--    workspace_member that exists, otherwise the first user in the system
--    (single-tenant homelab fallback).
INSERT INTO "workspaces" ("id", "slug", "name", "owner_user_id", "plan_tier", "archived_at", "created_at", "updated_at")
SELECT
  o.id,
  -- slug: lowercased, alphanum+dash, fallback to first 8 chars of UUID.
  COALESCE(
    NULLIF(
      regexp_replace(lower(trim(o.title)), '[^a-z0-9]+', '-', 'g'),
      ''
    ),
    substring(o.id::text from 1 for 8)
  ),
  COALESCE(NULLIF(trim(o.title), ''), 'Workspace'),
  COALESCE(
    o.created_by,
    (SELECT wm.user_id FROM "workspace_members" wm WHERE wm.workspace_id = o.id ORDER BY wm.created_at ASC LIMIT 1),
    (SELECT u.id FROM "users" u ORDER BY u.created_at ASC LIMIT 1)
  ),
  'free',
  o.archived_at,
  o.created_at,
  o.updated_at
FROM "objects" o
WHERE o.type = 'workspace';
--> statement-breakpoint

-- 2b. Disambiguate any duplicate slugs (e.g. two workspaces both titled "Tasks")
--     by suffixing with the short UUID. Only touches collisions.
UPDATE "workspaces" w
SET "slug" = w.slug || '-' || substring(w.id::text from 1 for 6)
WHERE w.id IN (
  SELECT id FROM (
    SELECT id, row_number() OVER (PARTITION BY slug ORDER BY created_at) AS rn
    FROM "workspaces"
  ) ranked
  WHERE ranked.rn > 1
);
--> statement-breakpoint

-- 2c. Safety net: if there is at least one user but NO workspace exists yet
--     (fresh DB on a brand-new Coolify deploy), seed a default one so the
--     NOT NULL FK swap below cannot fail at runtime.
INSERT INTO "workspaces" ("slug", "name", "owner_user_id")
SELECT 'default', 'Default Workspace', u.id
FROM "users" u
WHERE NOT EXISTS (SELECT 1 FROM "workspaces")
ORDER BY u.created_at ASC
LIMIT 1;
--> statement-breakpoint

-- 3. Drop old objects.id-based FKs on every anchor table.
ALTER TABLE "objects" DROP CONSTRAINT "objects_workspace_id_objects_id_fk";--> statement-breakpoint
ALTER TABLE "workspace_members" DROP CONSTRAINT "workspace_members_workspace_id_objects_id_fk";--> statement-breakpoint
ALTER TABLE "object_type_defs" DROP CONSTRAINT "object_type_defs_workspace_id_objects_id_fk";--> statement-breakpoint
ALTER TABLE "property_definitions" DROP CONSTRAINT "property_definitions_workspace_id_objects_id_fk";--> statement-breakpoint
ALTER TABLE "templates" DROP CONSTRAINT "templates_workspace_id_objects_id_fk";--> statement-breakpoint
ALTER TABLE "forms" DROP CONSTRAINT "forms_workspace_id_objects_id_fk";--> statement-breakpoint
ALTER TABLE "markdown_backlog_items" DROP CONSTRAINT "markdown_backlog_items_workspace_id_objects_id_fk";--> statement-breakpoint
ALTER TABLE "cursor_sync_mappings" DROP CONSTRAINT "cursor_sync_mappings_workspace_id_objects_id_fk";--> statement-breakpoint

-- 4. Backfill any orphan `workspace_id` values (rows whose old workspace
--    object was deleted before this migration ran). Reassign to the first
--    available workspace so the NOT NULL constraint below holds.
UPDATE "objects"
SET "workspace_id" = (SELECT id FROM "workspaces" ORDER BY created_at ASC LIMIT 1)
WHERE "workspace_id" IS NULL
  AND EXISTS (SELECT 1 FROM "workspaces");
--> statement-breakpoint

-- 5. Now objects.workspace_id is fully populated → flip to NOT NULL.
ALTER TABLE "objects" ALTER COLUMN "workspace_id" SET NOT NULL;--> statement-breakpoint

-- 6. Remove the obsolete type='workspace' rows from `objects` (they live in
--    `workspaces` now). Use a guard so this is a no-op on a fresh DB.
DELETE FROM "objects" WHERE "type" = 'workspace';--> statement-breakpoint

-- 7. New FK constraints + indexes.
ALTER TABLE "workspaces" ADD CONSTRAINT "workspaces_owner_user_id_users_id_fk" FOREIGN KEY ("owner_user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "workspaces_slug_unique" ON "workspaces" USING btree ("slug");--> statement-breakpoint
CREATE INDEX "workspaces_owner_user_id_idx" ON "workspaces" USING btree ("owner_user_id");--> statement-breakpoint
ALTER TABLE "objects" ADD CONSTRAINT "objects_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "workspace_members" ADD CONSTRAINT "workspace_members_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "object_type_defs" ADD CONSTRAINT "object_type_defs_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "property_definitions" ADD CONSTRAINT "property_definitions_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "templates" ADD CONSTRAINT "templates_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "forms" ADD CONSTRAINT "forms_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "markdown_backlog_items" ADD CONSTRAINT "markdown_backlog_items_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "cursor_sync_mappings" ADD CONSTRAINT "cursor_sync_mappings_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;
