import { T } from '@tldraw/validate'
import { TLRichText, richTextValidator, toRichText } from '../misc/TLRichText'
import { createShapePropsMigrationIds, createShapePropsMigrationSequence } from '../records/TLShape'
import { RecordProps } from '../recordsWithProps'
import {
	DefaultColorStyle,
	DefaultLabelColorStyle,
	TLDefaultColorStyle,
} from '../styles/TLColorStyle'
import { DefaultFontStyle, TLDefaultFontStyle } from '../styles/TLFontStyle'
import {
	DefaultHorizontalAlignStyle,
	TLDefaultHorizontalAlignStyle,
} from '../styles/TLHorizontalAlignStyle'
import { DefaultSizeStyle, TLDefaultSizeStyle } from '../styles/TLSizeStyle'
import {
	DefaultVerticalAlignStyle,
	TLDefaultVerticalAlignStyle,
} from '../styles/TLVerticalAlignStyle'
import { TLBaseShape } from './TLBaseShape'

/**
 * Properties for a note shape. Note shapes represent sticky notes or text annotations
 * with rich formatting capabilities and various styling options.
 *
 * @public
 * @example
 * ```ts
 * const noteProps: TLNoteShapeProps = {
 *   color: 'yellow',
 *   labelColor: 'black',
 *   size: 'm',
 *   font: 'draw',
 *   fontSizeAdjustment: null,
 *   align: 'middle',
 *   verticalAlign: 'middle',
 *   growY: 0,
 *   url: '',
 *   richText: toRichText('Hello **world**!'),
 *   scale: 1
 * }
 * ```
 */
export interface TLNoteShapeProps {
	/** Background color style of the note */
	color: TLDefaultColorStyle
	/** Text color style for the note content */
	labelColor: TLDefaultColorStyle
	/** Size style determining the font size and note dimensions */
	size: TLDefaultSizeStyle
	/** Font family style for the note text */
	font: TLDefaultFontStyle
	/** Ratio to scale the base font size when text needs to shrink to fit. Null means needs recomputation, 1 means no adjustment, and values less than 1 indicate shrinkage. */
	fontSizeAdjustment: number | null
	/** Horizontal alignment of text within the note */
	align: TLDefaultHorizontalAlignStyle
	/** Vertical alignment of text within the note */
	verticalAlign: TLDefaultVerticalAlignStyle
	/** Additional height growth for the note beyond its base size */
	growY: number
	/** Optional URL associated with the note for linking */
	url: string
	/** Rich text content with formatting like bold, italic, etc. */
	richText: TLRichText
	/** Scale factor applied to the note shape for display */
	scale: number
	/** User ID of the person who first edited the note text */
	textFirstEditedBy: string | null
}

/**
 * A note shape representing a sticky note or text annotation on the canvas.
 * Note shapes support rich text formatting, various styling options, and can
 * be used for annotations, reminders, or general text content.
 *
 * @public
 * @example
 * ```ts
 * const noteShape: TLNoteShape = {
 *   id: 'shape:note1',
 *   type: 'note',
 *   x: 100,
 *   y: 100,
 *   rotation: 0,
 *   index: 'a1',
 *   parentId: 'page:main',
 *   isLocked: false,
 *   opacity: 1,
 *   props: {
 *     color: 'light-blue',
 *     labelColor: 'black',
 *     size: 's',
 *     font: 'sans',
 *     fontSizeAdjustment: 0.85,
 *     align: 'start',
 *     verticalAlign: 'start',
 *     growY: 50,
 *     url: 'https://example.com',
 *     richText: toRichText('Important **note**!'),
 *     scale: 1
 *   },
 *   meta: {},
 *   typeName: 'shape'
 * }
 * ```
 */
export type TLNoteShape = TLBaseShape<'note', TLNoteShapeProps>

/**
 * Validation schema for note shape properties. Defines the runtime validation rules
 * for all properties of note shapes, ensuring data integrity and type safety.
 *
 * @public
 * @example
 * ```ts
 * import { noteShapeProps } from '@tldraw/tlschema'
 *
 * // Used internally by the validation system
 * const validator = T.object(noteShapeProps)
 * const validatedProps = validator.validate(someNoteProps)
 * ```
 */
export const noteShapeProps: RecordProps<TLNoteShape> = {
	color: DefaultColorStyle,
	labelColor: DefaultLabelColorStyle,
	size: DefaultSizeStyle,
	font: DefaultFontStyle,
	fontSizeAdjustment: T.positiveNumber.nullable(),
	align: DefaultHorizontalAlignStyle,
	verticalAlign: DefaultVerticalAlignStyle,
	growY: T.positiveNumber,
	url: T.linkUrl,
	richText: richTextValidator,
	scale: T.nonZeroNumber,
	textFirstEditedBy: T.string.nullable(),
}

const Versions = createShapePropsMigrationIds('note', {
	AddUrlProp: 1,
	RemoveJustify: 2,
	MigrateLegacyAlign: 3,
	AddVerticalAlign: 4,
	MakeUrlsValid: 5,
	AddFontSizeAdjustment: 6,
	AddScale: 7,
	AddLabelColor: 8,
	AddRichText: 9,
	AddRichTextAttrs: 10,
	AddFirstEditedBy: 11,
	MakeFontSizeAdjustmentRatio: 12,
})

/**
 * Version identifiers for note shape migrations. These version numbers track
 * significant schema changes over time, enabling proper data migration between versions.
 *
 * @public
 */
export { Versions as noteShapeVersions }

/**
 * Migration sequence for note shapes. Handles schema evolution over time by defining
 * how to upgrade and downgrade note shape data between different versions. Includes
 * migrations for URL properties, text alignment changes, vertical alignment addition,
 * font size adjustments, scaling support, label color, the transition from plain text to rich text,
 * and support for attrs property on richText.
 *
 * @public
 */
export const noteShapeMigrations = createShapePropsMigrationSequence({
	sequence: [
		{
			id: Versions.AddUrlProp,
			up: (props) => {
				props.url = ''
			},
			down: 'retired',
		},
		{
			id: Versions.RemoveJustify,
			up: (props) => {
				if (props.align === 'justify') {
					props.align = 'start'
				}
			},
			down: 'retired',
		},
		{
			id: Versions.MigrateLegacyAlign,
			up: (props) => {
				switch (props.align) {
					case 'start':
						props.align = 'start-legacy'
						return
					case 'end':
						props.align = 'end-legacy'
						return
					default:
						props.align = 'middle-legacy'
						return
				}
			},
			down: 'retired',
		},
		{
			id: Versions.AddVerticalAlign,
			up: (props) => {
				props.verticalAlign = 'middle'
			},
			down: 'retired',
		},
		{
			id: Versions.MakeUrlsValid,
			up: (props) => {
				if (!T.linkUrl.isValid(props.url)) {
					props.url = ''
				}
			},
			down: (_props) => {
				// noop
			},
		},
		{
			id: Versions.AddFontSizeAdjustment,
			up: (props) => {
				props.fontSizeAdjustment = 0
			},
			down: (props) => {
				delete props.fontSizeAdjustment
			},
		},
		{
			id: Versions.AddScale,
			up: (props) => {
				props.scale = 1
			},
			down: (props) => {
				delete props.scale
			},
		},
		{
			id: Versions.AddLabelColor,
			up: (props) => {
				props.labelColor = 'black'
			},
			down: (props) => {
				delete props.labelColor
			},
		},
		{
			id: Versions.AddRichText,
			up: (props) => {
				props.richText = toRichText(props.text)
				delete props.text
			},
			// N.B. Explicitly no down state so that we force clients to update.
			// down: (props) => {
			// 	delete props.richText
			// },
		},
		{
			id: Versions.AddRichTextAttrs,
			up: (_props) => {
				// noop - attrs is optional so old records are valid
			},
			down: (props) => {
				// Remove attrs from richText when migrating down
				if (props.richText && 'attrs' in props.richText) {
					delete props.richText.attrs
				}
			},
		},
		{
			id: Versions.AddFirstEditedBy,
			up: (props) => {
				props.textFirstEditedBy = null
			},
			down: (props) => {
				delete props.textFirstEditedBy
			},
		},
		{
			id: Versions.MakeFontSizeAdjustmentRatio,
			up: (props) => {
				// Old system stored 0 for "no adjustment" or an absolute pixel font size.
				// New system stores a ratio (1 = no adjustment, <1 = shrunk).
				// We can convert 0 → 1 (no adjustment), but non-zero values need
				// recomputation (null) since we don't know the base font size here.
				props.fontSizeAdjustment = props.fontSizeAdjustment === 0 ? 1 : null
			},
			down: (props) => {
				props.fontSizeAdjustment = 0
			},
		},
	],
})
