package registry

import (
	"context"
	"fmt"
	"strings"
)

// ListAppsForUser lists the registry entries visible to a user in a workspace.
//
// The parent app store remains responsible for workspace access authorization
// before delegating here; registry owns only app catalog visibility and manifest
// metadata assembly. App catalog authoring boundary reads may use the app
// registry tables; runtime installation, connection, subscription, and delivery
// paths must use Service Actor and Service Event tables as their source of
// truth.
func (store *Store) ListApps(ctx context.Context, workspaceID string) ([]AppDefinitionView, error) {
	rows, err := store.db.Query(ctx, `
SELECT
    app.id,
    app.slug,
    app.name,
    app.description,
    app.avatar_url,
    COALESCE(JSON_UNQUOTE(JSON_EXTRACT(COALESCE(app.config, JSON_OBJECT()), '$.appKind')), ''),
    app.visibility,
    CASE
      WHEN attribution.service_definition_id IS NOT NULL
       AND NOT (app.visibility = 'public' AND JSON_UNQUOTE(JSON_EXTRACT(COALESCE(app.config, JSON_OBJECT()), '$.appKind')) = 'first_party')
      THEN attribution.catalog_visibility
      ELSE ''
    END AS catalog_visibility,
    app.created_at,
    app.updated_at,
    app.latest_version,
    version.manifest
FROM service_definitions app
LEFT JOIN extension_catalog_attributions attribution
  ON attribution.workspace_id = $1
 AND attribution.service_definition_id = app.id
JOIN extension_definition_versions version
  ON version.service_definition_id = app.id
 AND version.version = app.latest_version
WHERE (
        app.visibility = 'public'
        AND JSON_UNQUOTE(JSON_EXTRACT(COALESCE(app.config, JSON_OBJECT()), '$.appKind')) = 'first_party'
      )
   OR attribution.service_definition_id IS NOT NULL
ORDER BY JSON_UNQUOTE(JSON_EXTRACT(COALESCE(app.config, JSON_OBJECT()), '$.appKind')) ASC, app.slug ASC
`, strings.TrimSpace(workspaceID))
	if err != nil {
		return nil, fmt.Errorf("list apps: %w", err)
	}
	defer rows.Close()

	apps := make([]AppDefinitionView, 0)
	for rows.Next() {
		var app AppDefinitionView
		var latestVersion string
		var manifestJSON []byte
		if err := rows.Scan(
			&app.ID,
			&app.Slug,
			&app.Name,
			&app.Description,
			&app.AvatarURL,
			&app.Kind,
			&app.Visibility,
			&app.CatalogVisibility,
			&app.CreatedAt,
			&app.UpdatedAt,
			&latestVersion,
			&manifestJSON,
		); err != nil {
			return nil, fmt.Errorf("scan app: %w", err)
		}

		isProduct := IsProductApp(app.ID, app.Slug)
		var runtimeCapability StoredAppRuntimeCapability
		if !isProduct {
			var supported bool
			runtimeCapability, supported = SupportedExternalMentionRuntimeCapability(app.Slug, app.Kind, manifestJSON)
			if !supported {
				continue
			}
		}

		manifest, err := ParseStoredManifest(manifestJSON)
		if err != nil {
			return nil, fmt.Errorf("decode app manifest: %w", err)
		}
		app.LatestVersion = strings.TrimSpace(latestVersion)
		app.LatestScopes = manifest.Scopes
		app.LatestConfigSchema = manifest.ConfigSchema
		if isProduct {
			runtimeCapability, err = ParseStoredAppRuntimeCapability(manifestJSON)
			if err != nil {
				return nil, fmt.Errorf("decode app App capability: %w", err)
			}
		}
		if runtimeCapability.Supported {
			var notifications *AppNotificationCapabilityView
			if runtimeCapability.Notifications.Supported {
				notifications = &AppNotificationCapabilityView{
					Targets: runtimeCapability.Notifications.Targets,
					Content: runtimeCapability.Notifications.Content,
				}
			}
			app.RuntimeCapability = &AppRuntimeCapabilityView{
				Supported:        true,
				Transports:       runtimeCapability.Transports,
				DefaultTransport: runtimeCapability.DefaultTransport,
				Events:           runtimeCapability.Events,
				Scopes:           runtimeCapability.Scopes,
				Notifications:    notifications,
				ConfigSchema:     runtimeCapability.ConfigSchema,
			}
		}
		apps = append(apps, app)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate apps: %w", err)
	}

	return apps, nil
}
