import { InjectionToken, Signal } from '@angular/core';
import { MatFormFieldAppearance } from '@angular/material/form-field';
import { FormControl, FormControlState, ValidatorFn, FormControlOptions, AsyncValidatorFn, AbstractControl } from '@angular/forms';
import { Subject } from 'rxjs';

interface ISdFormConfiguration {
    appearance?: MatFormFieldAppearance;
}
declare const SD_FORM_CONFIGURATION: InjectionToken<ISdFormConfiguration>;

declare class SdFormControl extends FormControl {
    sdChanges: Subject<boolean>;
    untouchChanges: Subject<boolean>;
    touchChanges: Subject<boolean>;
    pristineChanges: Subject<boolean>;
    constructor(formState?: FormControlState<any>, validatorOrOpts?: ValidatorFn | ValidatorFn[] | FormControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null);
    markAsUntouched(opts?: {
        onlySelf?: boolean;
        emitEvent?: boolean;
    }): void;
    markAsTouched(opts?: {
        onlySelf?: boolean;
        emitEvent?: boolean;
    }): void;
    markAsPristine(opts?: {
        onlySelf?: boolean;
        emitEvent?: boolean;
    }): void;
}

type SdCustomValidator = (value: any) => string | Promise<string>;
/**
 * Inline-error sentinel validator. Returns `{ inlineError: true }` so the form
 * template can render `<mat-error>{{ inlineError }}</mat-error>` whenever the
 * host component has a non-empty `[inlineError]` input. The error message
 * itself is read from the input — this validator only flags the state.
 *
 * why: each form component (input, textarea, select, checkbox, radio, switch,
 * date, datetime, input-number, autocomplete) previously declared the same
 * private `customInlineErrorValidator()` method. Centralized here so adding
 * another form component does not silently re-introduce the duplicate.
 */
declare const SdInlineErrorValidator: ValidatorFn;
declare const HandleSdCustomValidator: (func: SdCustomValidator) => AsyncValidatorFn;

interface SdSelectionData<T = any> {
    multiple?: boolean;
    values: any[];
    selectedItems: T[];
    value?: any;
    selectedItem?: T;
}

interface SdSearchReq<TData extends string | number = string> {
    type: 'SEARCH' | 'VALUE';
    searchText?: string;
    searchFields?: string[];
    value?: TData | TData[];
}
type SdSearch<T = any> = (args: SdSearchReq) => Promise<T[]>;

interface SdFormControlSnapshot<T> {
    value: T | undefined;
    disabled: boolean;
    invalid: boolean;
    touched: boolean;
}
/**
 * Wrap an AbstractControl Signal into a reactive snapshot signal.
 *
 * Re-emits on every value, status, touched, or dirty change so
 * downstream consumers can derive `data-disabled`, `data-value`,
 * `data-empty`, and `data-invalid` host-binding attributes from a
 * single, lazily evaluated signal.
 *
 * "invalid" is intentionally gated on `touched || dirty` so validation
 * errors are not surfaced until the user has interacted with the field.
 *
 * Must be called inside an Angular injection context (constructor,
 * field initialiser, or `runInInjectionContext`).
 */
declare function sdFormControlState<T = unknown>(control: Signal<AbstractControl<T> | null | undefined>): Signal<SdFormControlSnapshot<T>>;

/**
 * Three display states shared by sd-form-controls:
 * - `false`  → full edit chrome (input / dropdown).
 * - `true`   → static read-only view (`<sd-view>` text), no editor.
 * - `'inline'` → the editor is STILL rendered (so its panel works), but its chrome is
 *   hidden; the `<sd-view>` text is the visible face / trigger. Click the text to open
 *   the picker's panel. The text is retained while the panel is open — it only changes
 *   when a new value is committed (JIRA-style click-to-edit).
 */
type SdViewed = boolean | 'inline';
type SdViewedInput = SdViewed | '' | null | undefined;
/**
 * `viewed` input transform. Keeps `booleanAttribute` coercion so a bare attribute
 * (`<sd-select viewed>`) still resolves to `true`, but intercepts the literal
 * `'inline'` first — `booleanAttribute('inline')` would otherwise coerce it to `true`.
 */
declare function sdViewedTransform(v: SdViewedInput): SdViewed;
interface SdViewedInlineApi {
    /** `viewed() === 'inline'` — editor rendered but chrome hidden; sd-view text is the trigger face. */
    readonly isInline: Signal<boolean>;
    /** `viewed() === true` — static read-only view (no editor rendered). */
    readonly isViewed: Signal<boolean>;
    /** Open the picker from the inline text face. No-op unless `'inline'`. */
    enterInlineEdit(): void;
}
/**
 * Compose the tri-state `viewed` semantics into a control. `open` opens the control's
 * native picker (mat-select panel / mat-calendar / overlay). In `'inline'` mode the editor
 * is always rendered (chrome hidden via CSS), so `open()` can fire immediately on click —
 * no render-swap, the view text never disappears.
 *
 * @param viewed   the control's `viewed` input signal.
 * @param open     opens the control's picker; called by `enterInlineEdit`.
 * @param disabled the control's disabled state. why: a disabled `'inline'` field must behave
 *   like `viewed=true` (static, NOT click-to-edit) — you can't edit a disabled control.
 */
declare function sdViewedInline(viewed: Signal<SdViewed>, open?: () => void, disabled?: Signal<boolean>): SdViewedInlineApi;

export { HandleSdCustomValidator, SD_FORM_CONFIGURATION, SdFormControl, SdInlineErrorValidator, sdFormControlState, sdViewedInline, sdViewedTransform };
export type { ISdFormConfiguration, SdCustomValidator, SdFormControlSnapshot, SdSearch, SdSearchReq, SdSelectionData, SdViewed, SdViewedInlineApi, SdViewedInput };
